diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..1e8e25b
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,63 @@
+body {
+ background-color: #f6f8fa;
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ color: #24292f;
+}
+
+.navbar {
+ background-color: #23272a !important;
+}
+
+.navbar-brand {
+ font-weight: 700;
+ letter-spacing: -0.5px;
+}
+
+.card {
+ border: 1px solid #d0d7de;
+ border-radius: 6px;
+}
+
+.card-header {
+ border-bottom: 1px solid #d0d7de;
+ font-weight: 600;
+}
+
+.form-label {
+ font-weight: 500;
+ font-size: 0.9rem;
+}
+
+.btn-primary {
+ background-color: #5865f2;
+ border-color: #5865f2;
+}
+
+.btn-primary:hover {
+ background-color: #4752c4;
+ border-color: #4752c4;
+}
+
+.badge {
+ font-weight: 600;
+ padding: 0.5em 0.8em;
+}
+
+#logList {
+ max-height: 500px;
+ overflow-y: auto;
+}
+
+.list-group-item {
+ border-left: none;
+ border-right: none;
+ padding: 12px 16px;
+}
+
+.list-group-item:first-child {
+ border-top: none;
+}
+
+.font-weight-bold {
+ font-weight: 600;
+}
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..a851fdc
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,85 @@
+document.addEventListener('DOMContentLoaded', function() {
+ const configForm = document.getElementById('configForm');
+ const toastElement = document.getElementById('liveToast');
+ const toast = new bootstrap.Toast(toastElement);
+ const toastMessage = document.getElementById('toastMessage');
+
+ function showToast(message, type = 'info') {
+ toastMessage.textContent = message;
+ toast.show();
+ }
+
+ if (configForm) {
+ configForm.addEventListener('submit', function(e) {
+ e.preventDefault();
+ const formData = new FormData(configForm);
+
+ fetch('save_config.php', {
+ method: 'POST',
+ body: formData
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('Configuration saved successfully!', 'success');
+ } else {
+ showToast('Error: ' + data.error, 'danger');
+ }
+ })
+ .catch(error => {
+ showToast('An error occurred while saving.', 'danger');
+ });
+ });
+ }
+
+ const btnTestSahur = document.getElementById('btnTestSahur');
+ if (btnTestSahur) {
+ btnTestSahur.addEventListener('click', function() {
+ fetch('test_sahur.php')
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('Sahur test triggered! Check logs.', 'success');
+ setTimeout(() => location.reload(), 1500);
+ }
+ });
+ });
+ }
+
+ const refreshLogs = document.getElementById('refreshLogs');
+ if (refreshLogs) {
+ refreshLogs.addEventListener('click', function() {
+ location.reload();
+ });
+ }
+
+ const btnStartBot = document.getElementById('btnStartBot');
+ const btnStopBot = document.getElementById('btnStopBot');
+ const btnRestartBot = document.getElementById('btnRestartBot');
+
+ if (btnStartBot) {
+ btnStartBot.addEventListener('click', () => manageBot('start'));
+ }
+ if (btnStopBot) {
+ btnStopBot.addEventListener('click', () => manageBot('stop'));
+ }
+ if (btnRestartBot) {
+ btnRestartBot.addEventListener('click', () => manageBot('restart'));
+ }
+
+ function manageBot(action) {
+ fetch('manage_bot.php?action=' + action)
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast('Bot ' + action + 'ed successfully!', 'success');
+ setTimeout(() => location.reload(), 2000);
+ } else {
+ showToast('Error: ' + data.error, 'danger');
+ }
+ })
+ .catch(error => {
+ showToast('An error occurred.', 'danger');
+ });
+ }
+});
diff --git a/bot.log b/bot.log
new file mode 100644
index 0000000..6257f07
--- /dev/null
+++ b/bot.log
@@ -0,0 +1,323 @@
+[2026-02-14T17:28:37.135927+00:00] DiscordPHP.DEBUG: Initializing DiscordPHP v10.46.0 (DiscordPHP-Http: v10.8.0 & Gateway: v10) on PHP 8.2.29
+[2026-02-14T17:28:37.889141+00:00] DiscordPHP.DEBUG: BUCKET getapplications/@me queued REQ GET applications/@me
+[2026-02-14T17:28:37.889286+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:38.136506+00:00] DiscordPHP.DEBUG: BUCKET getgateway/bot queued REQ GET gateway/bot
+[2026-02-14T17:28:38.140843+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":1,"empty":true}
+[2026-02-14T17:28:41.865360+00:00] DiscordPHP.DEBUG: REQ GET gateway/bot successful
+[2026-02-14T17:28:41.865528+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":1,"empty":true}
+[2026-02-14T17:28:41.866063+00:00] DiscordPHP.INFO: gateway retrieved and set {"gateway":"wss://gateway.discord.gg/?v=10&encoding=json&compress=zlib-stream","session":{"total":1000,"remaining":998,"reset_after":86385055,"max_concurrency":1}}
+[2026-02-14T17:28:41.866152+00:00] DiscordPHP.DEBUG: session data received {"session":{"total":1000,"remaining":998,"reset_after":86385055,"max_concurrency":1}}
+[2026-02-14T17:28:41.866191+00:00] DiscordPHP.INFO: starting connection to websocket {"gateway":"wss://gateway.discord.gg/?v=10&encoding=json&compress=zlib-stream"}
+[2026-02-14T17:28:41.870607+00:00] DiscordPHP.DEBUG: REQ GET applications/@me successful
+[2026-02-14T17:28:41.870704+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:43.869452+00:00] DiscordPHP.INFO: websocket connection has been created
+[2026-02-14T17:28:43.874079+00:00] DiscordPHP.INFO: received hello
+[2026-02-14T17:28:43.874196+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":null}
+[2026-02-14T17:28:43.874338+00:00] DiscordPHP.INFO: heartbeat timer initialized {"interval":41250.0}
+[2026-02-14T17:28:43.874396+00:00] DiscordPHP.INFO: identifying {"payload":{"op":2,"d":{"token":"*****","properties":{"os":"Linux","browser":"DiscordBot (https://github.com/discord-php/DiscordPHP-HTTP, v10.8.0)","device":"DiscordBot (https://github.com/discord-php/DiscordPHP-HTTP, v10.8.0)","referrer":"https://github.com/discord-php/DiscordPHP","referring_domain":"https://github.com/discord-php/DiscordPHP"},"compress":true,"intents":53608189}}}
+[2026-02-14T17:28:44.113377+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":239.02297019958496}
+[2026-02-14T17:28:44.113623+00:00] DiscordPHP.DEBUG: ready packet received
+[2026-02-14T17:28:44.113664+00:00] DiscordPHP.DEBUG: resume_gateway_url received {"url":"wss://gateway-us-east1-d.discord.gg"}
+[2026-02-14T17:28:44.113714+00:00] DiscordPHP.DEBUG: discord trace received {"trace":["[\"gateway-prd-arm-us-east1-d-vgmq\",{\"micros\":153465,\"calls\":[\"id_created\",{\"micros\":536,\"calls\":[]},\"session_lookup_time\",{\"micros\":584,\"calls\":[]},\"session_lookup_finished\",{\"micros\":11,\"calls\":[]},\"discord-sessions-prd-2-147\",{\"micros\":152016,\"calls\":[\"start_session\",{\"micros\":149046,\"calls\":[\"discord-api-rpc-66c79f4bd4-dx7sf\",{\"micros\":83446,\"calls\":[\"get_user\",{\"micros\":15531},\"get_guilds\",{\"micros\":34892},\"send_scheduled_deletion_message\",{\"micros\":14},\"guild_join_requests\",{\"micros\":128},\"authorized_ip_coro\",{\"micros\":11},\"pending_payments\",{\"micros\":1260},\"apex_experiments\",{\"micros\":53495},\"sessions_experiments\",{\"micros\":17},\"user_activities\",{\"micros\":4},\"played_application_ids\",{\"micros\":4},\"linked_users\",{\"micros\":3},\"ad_personalization_toggles_disabled\",{\"micros\":4},\"regional_feature_config\",{\"micros\":3}]}]},\"starting_guild_connect\",{\"micros\":31,\"calls\":[]},\"presence_started\",{\"micros\":333,\"calls\":[]},\"guilds_started\",{\"micros\":55,\"calls\":[]},\"lobbies_started\",{\"micros\":1,\"calls\":[]},\"guilds_connect\",{\"micros\":2,\"calls\":[]},\"presence_connect\",{\"micros\":2468,\"calls\":[]},\"connect_finished\",{\"micros\":2484,\"calls\":[]},\"build_ready\",{\"micros\":10,\"calls\":[]},\"clean_ready\",{\"micros\":1,\"calls\":[]},\"optimize_ready\",{\"micros\":53,\"calls\":[]},\"split_ready\",{\"micros\":0,\"calls\":[]}]}]}]"]}
+[2026-02-14T17:28:44.114816+00:00] DiscordPHP.DEBUG: client created and session id stored {"session_id":"84f756fec6c7d47088527fadb37c1adb","user":{"id":"1471909193886859294","username":"AsepSahur","discriminator":"6954","global_name":null,"avatar":"https://cdn.discordapp.com/avatars/1471909193886859294/8a88b0710fa41f7eef469c3dedc30e27.webp?size=1024","bot":true,"system":null,"mfa_enabled":false,"banner":null,"accent_color":null,"locale":null,"verified":true,"email":null,"flags":0,"premium_type":null,"public_flags":null,"avatar_decoration_data":null,"collectibles":null,"primary_guild":null}}
+[2026-02-14T17:28:44.117231+00:00] DiscordPHP.INFO: stored guilds {"count":0,"unavailable":1}
+[2026-02-14T17:28:44.638804+00:00] DiscordPHP.DEBUG: guild available {"guild":"1428530728706117632","unavailable":1}
+[2026-02-14T17:28:44.869243+00:00] DiscordPHP.INFO: all guilds are now available {"count":1}
+[2026-02-14T17:28:44.869441+00:00] DiscordPHP.INFO: loadAllMembers option is disabled, not setting chunking up
+[2026-02-14T17:28:44.870230+00:00] DiscordPHP.INFO: voice class initialized
+[2026-02-14T17:28:44.870298+00:00] DiscordPHP.INFO: client is ready
+[2026-02-14T17:28:44.870346+00:00] DiscordPHP.INFO: The 'ready' event is deprecated and will be removed in a future version of DiscordPHP. Please use 'init' instead.
+Bot is ready!
+[2026-02-14T17:28:45.618209+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.622843+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:45.640624+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.640794+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.640890+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.641057+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.641160+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.641270+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:28:45.863705+00:00] DiscordPHP.INFO: received session id for voice session {"guild":"1428530728706117632","session_id":"84f756fec6c7d47088527fadb37c1adb"}
+[2026-02-14T17:28:45.864342+00:00] DiscordPHP.INFO: received token and endpoint for voice session {"guild":"1428530728706117632","token":"*****","endpoint":"c-fra20-5f509e9b.discord.media:2083"}
+Unhandled promise rejection with TypeError: explode(): Argument #2 ($string) must be of type string, null given in /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/ProcessAbstract.php:62
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/ProcessAbstract.php(62): explode()
+#1 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/Ffmpeg.php(55): Discord\Voice\Processes\ProcessAbstract::checkForExecutable()
+#2 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php(344): Discord\Voice\Processes\Ffmpeg::checkForFFmpeg()
+#3 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php(1381): Discord\Voice\VoiceClient->start()
+#4 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php(1425): Discord\Voice\VoiceClient->boot()
+#5 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/Manager.php(214): Discord\Voice\VoiceClient->setData()
+#6 /home/ubuntu/executor/workspace/vendor/discord-php-helpers/voice/src/Discord/Voice/Manager.php(112): Discord\Voice\Manager->serverUpdate()
+#7 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): Discord\Voice\Manager->Discord\Voice\{closure}()
+#8 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(913): Discord\Discord->emit()
+#9 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Discord->Discord\{closure}()
+#10 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#11 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#12 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#13 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#14 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Deferred->resolve()
+#15 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(946): React\Promise\Internal\FulfilledPromise->then()
+#16 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(794): Discord\Discord->handleDispatch()
+#17 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(748): Discord\Discord->processWsMessage()
+#18 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): Discord\Discord->handleWsMessage()
+#19 /home/ubuntu/executor/workspace/vendor/ratchet/pawl/src/WebSocket.php(72): Ratchet\Client\WebSocket->emit()
+#20 /home/ubuntu/executor/workspace/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php(233): Ratchet\Client\WebSocket->Ratchet\Client\{closure}()
+#21 /home/ubuntu/executor/workspace/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php(179): Ratchet\RFC6455\Messaging\MessageBuffer->processData()
+#22 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): Ratchet\RFC6455\Messaging\MessageBuffer->onData()
+#23 /home/ubuntu/executor/workspace/vendor/react/stream/src/Util.php(71): Evenement\EventEmitter->emit()
+#24 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Stream\Util::React\Stream\{closure}()
+#25 /home/ubuntu/executor/workspace/vendor/react/stream/src/DuplexResourceStream.php(209): Evenement\EventEmitter->emit()
+#26 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(246): React\Stream\DuplexResourceStream->handleData()
+#27 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(213): React\EventLoop\StreamSelectLoop->waitForStreamActivity()
+#28 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(1822): React\EventLoop\StreamSelectLoop->run()
+#29 /home/ubuntu/executor/workspace/bot.php(230): Discord\Discord->run()
+#30 {main}
+[2026-02-14T17:28:48.616884+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:28:48.617013+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:48.617133+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:48.863972+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:28:48.864090+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:48.864191+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:49.114102+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:28:49.114215+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:49.114312+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:49.364389+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:28:49.364510+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:28:49.364591+00:00] DiscordPHP.INFO: BUCKET postapplications/:application_id/commands expecting rate limit, timer interval 18740 ms
+[2026-02-14T17:29:04.628833+00:00] DiscordPHP.DEBUG: BUCKET postinteractions/:interaction_id/:interaction_token/callback queued REQ POST interactions/1472283518062297088/aW50ZXJhY3Rpb246MTQ3MjI4MzUxODA2MjI5NzA4ODpFUXVwaWRGbFVESXUzbFlOSENnWEc3ZVBxeThjMkIyVTF4OURwbTR2Wjlhb1hiZzRRSm12SVR3SzNYa05VS01SQ1UzYVhtYlJDZDdpN29aYWlhekpWb3RUQ3duUWhUYXZtaVRhMXBZZEtBVk80SklBQVZ3YmtCTDhncTg1Tmp2UA/callback
+[2026-02-14T17:29:04.628966+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:29:07.366211+00:00] DiscordPHP.WARNING: REQ POST interactions/1472283518062297088/aW50ZXJhY3Rpb246MTQ3MjI4MzUxODA2MjI5NzA4ODpFUXVwaWRGbFVESXUzbFlOSENnWEc3ZVBxeThjMkIyVTF4OURwbTR2Wjlhb1hiZzRRSm12SVR3SzNYa05VS01SQ1UzYVhtYlJDZDdpN29aYWlhekpWb3RUQ3duUWhUYXZtaVRhMXBZZEtBVk80SklBQVZ3YmtCTDhncTg1Tmp2UA/callback failed: Discord\Http\Exceptions\BadRequestException: Bad Request - {
+ "message": "Interaction has already been acknowledged.",
+ "code": 40060
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:451
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Http\Http->Discord\Http\{closure}()
+#2 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#3 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#4 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#5 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#6 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(90): React\Promise\Deferred->[2026-02-14T17:29:07.879358+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":10}
+[2026-02-14T17:29:08.114567+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":234.9832057952881}
+[2026-02-14T17:29:12.363130+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:29:14.363657+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:29:14.377102+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:29:22.619772+00:00] DiscordPHP.DEBUG: BUCKET postinteractions/:interaction_id/:interaction_token/callback queued REQ POST interactions/1472283593102327903/aW50ZXJhY3Rpb246MTQ3MjI4MzU5MzEwMjMyNzkwMzpiMHZYVExrTTl5eHE2TjRKZ2d6d1lETUc4a2xoc1dHaVM2OWlwMk9veUtESHIyR2VFaUxEOUdCNG16aEpsWll2eU9zNVdoR3F2dkxVWExrek42SkxCN3duR3FCYVp6WGhNb05oUUtSMGhRMDZhYjhPWjVjMzdPc3RLeTVKanpieg/callback
+[2026-02-14T17:29:22.619923+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:29:24.365387+00:00] DiscordPHP.WARNING: REQ POST interactions/1472283593102327903/aW50ZXJhY3Rpb246MTQ3MjI4MzU5MzEwMjMyNzkwMzpiMHZYVExrTTl5eHE2TjRKZ2d6d1lETUc4a2xoc1dHaVM2OWlwMk9veUtESHIyR2VFaUxEOUdCNG16aEpsWll2eU9zNVdoR3F2dkxVWExrek42SkxCN3duR3FCYVp6WGhNb05oUUtSMGhRMDZhYjhPWjVjMzdPc3RLeTVKanpieg/callback failed: Discord\Http\Exceptions\NotFoundException: Not Found - {
+ "message": "Unknown interaction",
+ "code": 10062
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:457
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Http\Http->Discord\Http\{closure}()
+#2 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#3 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#4 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#5 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#6 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(90): React\Promise\Deferred->resolve()
+#7 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#8 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#9 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#10 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#11 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#12 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(181): React\Promise\Internal\FulfilledPromise->then()
+#13 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#14 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#15 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#16 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(178): React\Promise\Internal\FulfilledPromise->then()
+#17 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#18 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#19 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(193): React\Promise\Promise::React\Promise\{closure}()
+#20 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#21 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(50): Evenement\EventEmitter->emit()
+#22 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(151): React\Http\Io\ReadableBodyStream->close()
+#23 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(33): React\Http\Io\ReadableBodyStream->handleEnd()
+#24 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ReadableBodyStream->React\Http\Io\{closure}()
+#25 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/CloseProtectionStream.php(96): Evenement\EventEmitter->emit()
+#26 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ClientRequestStream.php(228): React\Http\Io\CloseProtectionStream->handleData()
+#27 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ClientRequestStream->handleData()
+#28 /home/ubuntu/executor/workspace/vendor/react/stream/src/Util.php(71): Evenement\EventEmitter->emit()
+#29 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Stream\Util::React\Stream\{closure}()
+#30 /home/ubuntu/executor/workspace/vendor/react/stream/src/DuplexResourceStream.php(209): Evenement\EventEmitter->emit()
+#31 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(246): React\Stream\DuplexResourceStream->handleData()
+#32 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(213): React\EventLoop\StreamSelectLoop->waitForStreamActivity()
+#33 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(1822): React\EventLoop\StreamSelectLoop->run()
+#34 /home/ubuntu/executor/workspace/bot.php(230): Discord\Discord->run()
+#35 {main}
+[2026-02-14T17:29:24.365555+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+Unhandled promise rejection with Discord\Http\Exceptions\NotFoundException: Not Found - {
+ "message": "Unknown interaction",
+ "code": 10062
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:457
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Http\Http->Discord\Http\{closure}()
+#2 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#3 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#4 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#5 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#6 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(90): React\Promise\Deferred->resolve()
+#7 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#8 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#9 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#10 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#11 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#12 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(181): React\Promise\Internal\FulfilledPromise->then()
+#13 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#14 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#15 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#16 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(178): React\Promise\Internal\FulfilledPromise->then()
+#17 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#18 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#19 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(193): React\Promise\Promise::React\Promise\{closure}()
+#20 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#21 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(50): Evenement\EventEmitter->emit()
+#22 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(151): React\Http\Io\ReadableBodyStream->close()
+#23 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(33): React\Http\Io\ReadableBodyStream->handleEnd()
+#24 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ReadableBodyStream->React\Http\Io\{closure}()
+#25 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/CloseProtectionStream.php(96): Evenement\EventEmitter->emit()
+#26 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ClientRequestStream.php(228): React\Http\Io\CloseProtectionStream->handleData()
+#27 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ClientRequestSt[2026-02-14T17:29:24.613787+00:00] DiscordPHP.DEBUG: REQ POST interactions/1472283593102327903/aW50ZXJhY3Rpb246MTQ3MjI4MzU5MzEwMjMyNzkwMzpiMHZYVExrTTl5eHE2TjRKZ2d6d1lETUc4a2xoc1dHaVM2OWlwMk9veUtESHIyR2VFaUxEOUdCNG16aEpsWll2eU9zNVdoR3F2dkxVWExrek42SkxCN3duR3FCYVp6WGhNb05oUUtSMGhRMDZhYjhPWjVjMzdPc3RLeTVKanpieg/callback successful
+[2026-02-14T17:29:24.613909+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:29:25.124345+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":11}
+[2026-02-14T17:29:25.362816+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":238.2359504699707}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:29:43.873337+00:00] DiscordPHP.DEBUG: resetting payload count {"count":4}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:06.375851+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":11}
+[2026-02-14T17:30:06.613000+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":236.83714866638184}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:13.652063+00:00] DiscordPHP.DEBUG: BUCKET postinteractions/:interaction_id/:interaction_token/callback queued REQ POST interactions/1472283808261869610/aW50ZXJhY3Rpb246MTQ3MjI4MzgwODI2MTg2OTYxMDpYRTVaZ3pyQ0IxMzBDcFVMUzI0dWNvZ0hXYXh6T0JJR0J3akVHWmxUTDhRTGprZWhndTBTS1Y2eFJLM2RWWnR0SURyUUZwTGppOXZUb0pZdW03bzBkSlZ5dkphNlE1aFBOUEpDaEVUT3N4QjZPU2hZOVBTa2NmUVE1aUhxVDR6Mg/callback
+[2026-02-14T17:30:13.652248+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:15.613329+00:00] DiscordPHP.WARNING: REQ POST interactions/1472283808261869610/aW50ZXJhY3Rpb246MTQ3MjI4MzgwODI2MTg2OTYxMDpYRTVaZ3pyQ0IxMzBDcFVMUzI0dWNvZ0hXYXh6T0JJR0J3akVHWmxUTDhRTGprZWhndTBTS1Y2eFJLM2RWWnR0SURyUUZwTGppOXZUb0pZdW03bzBkSlZ5dkphNlE1aFBOUEpDaEVUT3N4QjZPU2hZOVBTa2NmUVE1aUhxVDR6Mg/callback failed: Discord\Http\Exceptions\BadRequestException: Bad Request - {
+ "message": "Interaction has already been acknowledged.",
+ "code": 40060
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:451
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:24.868270+00:00] DiscordPHP.DEBUG: BUCKET postinteractions/:interaction_id/:interaction_token/callback queued REQ POST interactions/1472283854537625784/aW50ZXJhY3Rpb246MTQ3MjI4Mzg1NDUzNzYyNTc4NDpWUldIRzVyVldKTnh2bGpTZE9DZm4xOG5kb3RLZDJkTWFTMU54ZktsUlpOaUpKYkVHT1ZQVDh0NEZRdkF5VEZxSThMVVk3V2hOQWZvS0szZnRNNEc0YktCck9VaXBBMWJWOGc4QURzTVhsdXRQY3ZUWFFCRUxXYXlBQ0Q0UzJLMQ/callback
+[2026-02-14T17:30:24.868386+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:30:26.365024+00:00] DiscordPHP.WARNING: REQ POST interactions/1472283854537625784/aW50ZXJhY3Rpb246MTQ3MjI4Mzg1NDUzNzYyNTc4NDpWUldIRzVyVldKTnh2bGpTZE9DZm4xOG5kb3RLZDJkTWFTMU54ZktsUlpOaUpKYkVHT1ZQVDh0NEZRdkF5VEZxSThMVVk3V2hOQWZvS0szZnRNNEc0YktCck9VaXBBMWJWOGc4QURzTVhsdXRQY3ZUWFFCRUxXYXlBQ0Q0UzJLMQ/callback failed: Discord\Http\Exceptions\BadRequestException: Bad Request - {
+ "message": "Interaction has already been acknowledged.",
+ "code": 40060
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:451
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Http\Http->Discord\Http\{closure}()
+#2 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#3 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#4 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#5 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#6 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(90): React\Promise\Deferred->resolve()
+#7 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#8 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#9 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#10 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#11 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#12 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(181): React\Promise\Internal\FulfilledPromise->then()
+#13 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#14 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#15 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#16 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(178): React\Promise\Internal\FulfilledPromise->then()
+#17 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#18 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#19 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(193): React\Promise\Promise::React\Promise\{closure}()
+#20 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#21 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(50): Evenement\EventEmitter->emit()
+#22 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(151): React\Http\Io\ReadableBodyStream->close()
+#23 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(33): React\Http\Io\ReadableBodyStream->handleEnd()
+#24 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ReadableBodyStream->React\Http\Io\{closure}()
+#25 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/CloseProtectionStream.php(96): Evenement\EventEmitter->emit()
+#26 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ClientRequestStream.php(228): React\Http\Io\CloseProtectionStream->handleData()
+#27 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ClientRequestStream->handleData()
+#28 /home/ubuntu/executor/workspace/vendor/react/stream/src/Util.php(71): Evenement\EventEmitter->emit()
+#29 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Stream\Util::React\Stream\{closure}()
+#30 /home/ubuntu/executor/workspace/vendor/react/stream/src/DuplexResourceStream.php(209): Evenement\EventEmitter->emit()
+#31 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(246): React\Stream\DuplexResourceStream->handleData()
+#32 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(213): React\EventLoop\StreamSelectLoop->waitForStreamActivity()
+#33 /home/ubuntu/executor/workspace/vendor/team-reflex/discord-php/src/Discord/Discord.php(1822): React\EventLoop\StreamSelectLoop->run()
+#34 /home/ubuntu/executor/workspace/bot.php(230): Discord\Discord->run()
+#35 {main}
+[2026-02-14T17:30:26.365139+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+Unhandled promise rejection with Discord\Http\Exceptions\BadRequestException: Bad Request - {
+ "message": "Interaction has already been acknowledged.",
+ "code": 40060
+} in /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php:451
+Stack trace:
+#0 /home/ubuntu/executor/workspace/vendor/discord-php/http/src/Discord/HttpTrait.php(287): Discord\Http\Http->handleError()
+#1 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): Discord\Http\Http->Discord\Http\{closure}()
+#2 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#3 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#4 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#5 /home/ubuntu/executor/workspace/vendor/react/promise/src/Deferred.php(45): React\Promise\Promise::React\Promise\{closure}()
+#6 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(90): React\Promise\Deferred->resolve()
+#7 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#8 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(174): React\Promise\Internal\FulfilledPromise->then()
+#9 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#10 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#11 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#12 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(181): React\Promise\Internal\FulfilledPromise->then()
+#13 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#14 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#15 /home/ubuntu/executor/workspace/vendor/react/promise/src/Internal/FulfilledPromise.php(47): React\Promise\Promise::React\Promise\{closure}()
+#16 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(178): React\Promise\Internal\FulfilledPromise->then()
+#17 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(222): React\Promise\Promise::React\Promise\{closure}()
+#18 /home/ubuntu/executor/workspace/vendor/react/promise/src/Promise.php(287): React\Promise\Promise->settle()
+#19 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/Transaction.php(193): React\Promise\Promise::React\Promise\{closure}()
+#20 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\Transaction->React\Http\Io\{closure}()
+#21 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(50): Evenement\EventEmitter->emit()
+#22 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(151): React\Http\Io\ReadableBodyStream->close()
+#23 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ReadableBodyStream.php(33): React\Http\Io\ReadableBodyStream->handleEnd()
+#24 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ReadableBodyStream->React\Http\Io\{closure}()
+#25 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/CloseProtectionStream.php(96): Evenement\EventEmitter->emit()
+#26 /home/ubuntu/executor/workspace/vendor/react/http/src/Io/ClientRequestStream.php(228): React\Http\Io\CloseProtectionStream->handleData()
+#27 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Http\Io\ClientRequestStream->handleData()
+#28 /home/ubuntu/executor/workspace/vendor/react/stream/src/Util.php(71): Evenement\EventEmitter->emit()
+#29 /home/ubuntu/executor/workspace/vendor/evenement/evenement/src/EventEmitterTrait.php(143): React\Stream\Util::React\Stream\{closure}()
+#30 /home/ubuntu/executor/workspace/vendor/react/stream/src/DuplexResourceStream.php(209): Evenement\EventEmitter->emit()
+#31 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(246): React\Stream\DuplexResourceStream->handleData()
+#32 /home/ubuntu/executor/workspace/vendor/react/event-loop/src/StreamSelectLoop.php(213): React\EventLoop\StreamSelectLoop->waitForStreamActivity()
+#33 /home/ubuntu/executor/workspace/vendor/team-reflCould not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:43.875969+00:00] DiscordPHP.DEBUG: resetting payload count {"count":1}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:30:47.626211+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":15}
+[2026-02-14T17:30:47.864167+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":237.73479461669922}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+:31:11.632401+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":16}
+[2026-02-14T17:31:11.867756+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":235.0931167602539}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
diff --git a/bot.php b/bot.php
new file mode 100644
index 0000000..ae4f3b3
--- /dev/null
+++ b/bot.php
@@ -0,0 +1,278 @@
+query("SELECT setting_key, setting_value FROM bot_settings")->fetchAll(PDO::FETCH_KEY_PAIR);
+
+$token = $settings['bot_token'] ?? '';
+$vcId = $settings['voice_channel_id'] ?? '1457687430189682781';
+$sahurTime = $settings['sahur_time'] ?? '03:00';
+$sahurSource = $settings['sahur_source'] ?? 'sahur.mp3';
+
+if (empty($token)) {
+ echo "Error: Bot token is missing in settings.\n";
+ exit(1);
+}
+
+$discord = new Discord([
+ 'token' => $token,
+ 'intents' => Intents::getDefaultIntents() | Intents::GUILD_VOICE_STATES | Intents::MESSAGE_CONTENT,
+]);
+
+$voiceClient = null;
+$queue = [];
+$currentTrack = null;
+
+function logToDb($message, $level = 'info') {
+ $db = db();
+ $stmt = $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES (?, ?)");
+ $stmt->execute([$message, $level]);
+}
+
+$discord->on('ready', function (Discord $discord) use (&$voiceClient, $vcId, $sahurTime) {
+ echo "Bot is ready!", PHP_EOL;
+ logToDb("Bot is online and ready.");
+
+ // Update status in DB
+ $db = db();
+ $db->prepare("UPDATE bot_settings SET setting_value = 'online' WHERE setting_key = 'bot_status'")->execute();
+
+ // Auto-join VC
+ joinVoiceChannel($discord, $vcId);
+
+ // Schedule Sahur
+ $discord->getLoop()->addPeriodicTimer(60, function () use ($discord, $sahurTime, $vcId) {
+ $now = date('H:i');
+ if ($now === $sahurTime) {
+ echo "Sahur time! Playing audio...\n";
+ logToDb("Sahur time triggered. Playing audio.");
+ playSahur($discord, $vcId);
+ }
+ });
+
+ // Register Slash Commands
+ registerCommands($discord);
+});
+
+function joinVoiceChannel(Discord $discord, $channelId, $interaction = null) {
+ global $voiceClient;
+
+ if ($voiceClient) {
+ $msg = "I am already in a voice channel: " . ($voiceClient->getChannel()->name ?? 'Unknown');
+ if ($interaction) {
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent($msg));
+ }
+ return;
+ }
+
+ $channel = $discord->getChannel($channelId);
+
+ // If channel not in cache, try to find it in guilds
+ if (!$channel) {
+ foreach ($discord->guilds as $guild) {
+ $channel = $guild->channels->get('id', $channelId);
+ if ($channel) break;
+ }
+ }
+
+ if ($channel instanceof Channel && $channel->type === Channel::TYPE_VOICE) {
+ if ($interaction) {
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("Connecting to voice channel: " . $channel->name . "..."));
+ }
+
+ $discord->joinVoiceChannel($channel)->then(function (VoiceClient $vc) use (&$voiceClient, $discord, $channelId, $channel, $interaction) {
+ $voiceClient = $vc;
+ echo "Joined voice channel: " . $channel->name, PHP_EOL;
+ logToDb("Joined voice channel: " . $channel->name);
+
+ if ($interaction) {
+ $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Successfully joined voice channel: " . $channel->name));
+ }
+
+ $vc->on('error', function ($e) use ($discord, $channelId) {
+ echo "Voice Error: " . $e->getMessage(), PHP_EOL;
+ logToDb("Voice error: " . $e->getMessage(), 'error');
+ });
+ }, function ($e) use ($interaction) {
+ $errorMsg = "Could not join voice channel: " . $e->getMessage();
+ echo $errorMsg, PHP_EOL;
+ logToDb($errorMsg, 'error');
+ if ($interaction) {
+ try {
+ $interaction->updateOriginalResponse(MessageBuilder::new()->setContent($errorMsg));
+ } catch (\Exception $ex) {
+ // Fallback if update fails
+ }
+ }
+ });
+ } else {
+ $errorMsg = "Voice channel not found or invalid ID: " . $channelId;
+ if ($interaction) {
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent($errorMsg));
+ }
+ echo $errorMsg, PHP_EOL;
+ }
+}
+
+function playSahur(Discord $discord, $vcId) {
+ global $voiceClient, $sahurSource;
+ if (!$voiceClient) {
+ joinVoiceChannel($discord, $vcId);
+ // Wait a bit for connection
+ $discord->getLoop()->addTimer(3, function() use ($discord, $vcId) {
+ playSahur($discord, $vcId);
+ });
+ return;
+ }
+
+ if (filter_var($sahurSource, FILTER_VALIDATE_URL)) {
+ echo "Playing sahur from URL: $sahurSource\n";
+ $cmd = "yt-dlp -g -f bestaudio \"$sahurSource\"";
+ exec($cmd, $output, $resultCode);
+ if ($resultCode === 0 && !empty($output[0])) {
+ $voiceClient->playRawStream($output[0]);
+ }
+ } else {
+ if (!file_exists($sahurSource)) {
+ $error = "Error: Audio file '$sahurSource' not found. Please upload it to the bot's root directory.";
+ echo $error, PHP_EOL;
+ logToDb($error, 'error');
+ return;
+ }
+ echo "Playing sahur from file: $sahurSource\n";
+ $voiceClient->playFile($sahurSource)->done(function() {
+ echo "Finished playing sahur audio.\n";
+ });
+ }
+}
+
+function registerCommands(Discord $discord) {
+ $commands = [
+ CommandBuilder::new()
+ ->setName('join')
+ ->setDescription('Join the default voice channel'),
+ CommandBuilder::new()
+ ->setName('out')
+ ->setDescription('Leave the voice channel'),
+ CommandBuilder::new()
+ ->setName('status')
+ ->setDescription('Check bot status'),
+ CommandBuilder::new()
+ ->setName('testsahur')
+ ->setDescription('Test play sahur audio now'),
+ CommandBuilder::new()
+ ->setName('play')
+ ->setDescription('Play music from URL')
+ ->addOption((new Option($discord))
+ ->setName('url')
+ ->setDescription('The URL of the song (YouTube, SoundCloud, etc.)')
+ ->setType(Option::STRING)
+ ->setRequired(true)),
+ CommandBuilder::new()
+ ->setName('skip')
+ ->setDescription('Skip current song'),
+ CommandBuilder::new()
+ ->setName('stop')
+ ->setDescription('Stop music and clear queue'),
+ ];
+
+ foreach ($commands as $command) {
+ $discord->application->commands->save(
+ $discord->application->commands->create($command->toArray())
+ );
+ }
+}
+
+$discord->on(Event::INTERACTION_CREATE, function (Interaction $interaction, Discord $discord) use (&$voiceClient, $vcId) {
+ $command = $interaction->data->name;
+
+ switch ($command) {
+ case 'join':
+ $userChannel = $interaction->member->getVoiceChannel();
+ if ($userChannel) {
+ joinVoiceChannel($discord, $userChannel->id, $interaction);
+ } else {
+ joinVoiceChannel($discord, $vcId, $interaction);
+ }
+ break;
+ case 'out':
+ if ($voiceClient) {
+ $channelName = $voiceClient->getChannel()->name ?? 'channel';
+ $voiceClient->close();
+ $voiceClient = null;
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("Left voice channel: $channelName."));
+ } else {
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("I'm not in a voice channel."));
+ }
+ break;
+ case 'status':
+ $vcStatus = $voiceClient ? "Connected to: " . ($voiceClient->getChannel()->name ?? 'Unknown') : "Not connected to any voice channel.";
+ $status = "Bot is online.\n$vcStatus\nTarget default VC: $vcId";
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent($status));
+ break;
+ case 'testsahur':
+ playSahur($discord, $vcId);
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("Testing sahur audio..."));
+ break;
+ case 'play':
+ $url = $interaction->data->options['url']->value;
+ handlePlay($interaction, $discord, $url);
+ break;
+ case 'skip':
+ if ($voiceClient) {
+ $voiceClient->stop();
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("Skipped."));
+ }
+ break;
+ case 'stop':
+ global $queue;
+ $queue = [];
+ if ($voiceClient) {
+ $voiceClient->stop();
+ }
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("Stopped and cleared queue."));
+ break;
+ }
+});
+
+function handlePlay(Interaction $interaction, Discord $discord, string $url) {
+ global $voiceClient, $queue, $vcId;
+
+ if (!$voiceClient) {
+ $interaction->respondWithMessage(MessageBuilder::new()->setContent("I need to be in a voice channel first. use `/join`"));
+ return;
+ }
+
+ $interaction->acknowledge();
+
+ // Simple play logic using yt-dlp
+ $cmd = "yt-dlp -g -f bestaudio \"$url\"";
+ exec($cmd, $output, $resultCode);
+
+ if ($resultCode === 0 && !empty($output[0])) {
+ $audioUrl = $output[0];
+ $voiceClient->playRawStream($audioUrl)->done(function() use ($interaction) {
+ // Logic for next in queue could go here
+ });
+ $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Now playing: $url"));
+ } else {
+ $interaction->updateOriginalResponse(MessageBuilder::new()->setContent("Error: Could not fetch audio for that URL."));
+ }
+}
+
+$discord->run();
diff --git a/bot.pid b/bot.pid
new file mode 100644
index 0000000..0a16967
--- /dev/null
+++ b/bot.pid
@@ -0,0 +1 @@
+25412
\ No newline at end of file
diff --git a/bot_output.log b/bot_output.log
new file mode 100644
index 0000000..c24fd4c
--- /dev/null
+++ b/bot_output.log
@@ -0,0 +1,123 @@
+[2026-02-14T17:34:29.469151+00:00] DiscordPHP.DEBUG: Initializing DiscordPHP v10.46.0 (DiscordPHP-Http: v10.8.0 & Gateway: v10) on PHP 8.2.29
+[2026-02-14T17:34:29.968583+00:00] DiscordPHP.DEBUG: BUCKET getapplications/@me queued REQ GET applications/@me
+[2026-02-14T17:34:29.968742+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:29.990065+00:00] DiscordPHP.DEBUG: BUCKET getgateway/bot queued REQ GET gateway/bot
+[2026-02-14T17:34:29.990183+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":1,"empty":true}
+[2026-02-14T17:34:32.473338+00:00] DiscordPHP.DEBUG: REQ GET gateway/bot successful
+[2026-02-14T17:34:32.473473+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":1,"empty":true}
+[2026-02-14T17:34:32.475855+00:00] DiscordPHP.INFO: gateway retrieved and set {"gateway":"wss://gateway.discord.gg/?v=10&encoding=json&compress=zlib-stream","session":{"total":1000,"remaining":997,"reset_after":86034402,"max_concurrency":1}}
+[2026-02-14T17:34:32.475972+00:00] DiscordPHP.DEBUG: session data received {"session":{"total":1000,"remaining":997,"reset_after":86034402,"max_concurrency":1}}
+[2026-02-14T17:34:32.476017+00:00] DiscordPHP.INFO: starting connection to websocket {"gateway":"wss://gateway.discord.gg/?v=10&encoding=json&compress=zlib-stream"}
+[2026-02-14T17:34:32.742743+00:00] DiscordPHP.DEBUG: REQ GET applications/@me successful
+[2026-02-14T17:34:32.742880+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:33.237129+00:00] DiscordPHP.INFO: websocket connection has been created
+[2026-02-14T17:34:33.468708+00:00] DiscordPHP.INFO: received hello
+[2026-02-14T17:34:33.468844+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":null}
+[2026-02-14T17:34:33.469003+00:00] DiscordPHP.INFO: heartbeat timer initialized {"interval":41250.0}
+[2026-02-14T17:34:33.469063+00:00] DiscordPHP.INFO: identifying {"payload":{"op":2,"d":{"token":"*****","properties":{"os":"Linux","browser":"DiscordBot (https://github.com/discord-php/DiscordPHP-HTTP, v10.8.0)","device":"DiscordBot (https://github.com/discord-php/DiscordPHP-HTTP, v10.8.0)","referrer":"https://github.com/discord-php/DiscordPHP","referring_domain":"https://github.com/discord-php/DiscordPHP"},"compress":true,"intents":53608189}}}
+[2026-02-14T17:34:33.714362+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":245.3451156616211}
+[2026-02-14T17:34:33.714619+00:00] DiscordPHP.DEBUG: ready packet received
+[2026-02-14T17:34:33.714682+00:00] DiscordPHP.DEBUG: resume_gateway_url received {"url":"wss://gateway-us-east1-d.discord.gg"}
+[2026-02-14T17:34:33.714724+00:00] DiscordPHP.DEBUG: discord trace received {"trace":["[\"gateway-prd-arm-us-east1-d-w99h\",{\"micros\":136287,\"calls\":[\"id_created\",{\"micros\":756,\"calls\":[]},\"session_lookup_time\",{\"micros\":406,\"calls\":[]},\"session_lookup_finished\",{\"micros\":8,\"calls\":[]},\"discord-sessions-prd-2-153\",{\"micros\":134815,\"calls\":[\"start_session\",{\"micros\":120390,\"calls\":[\"discord-api-rpc-66c79f4bd4-wn8x4\",{\"micros\":42287,\"calls\":[\"get_user\",{\"micros\":7101},\"get_guilds\",{\"micros\":12443},\"send_scheduled_deletion_message\",{\"micros\":15},\"guild_join_requests\",{\"micros\":1445},\"authorized_ip_coro\",{\"micros\":8},\"pending_payments\",{\"micros\":1460},\"apex_experiments\",{\"micros\":69210},\"sessions_experiments\",{\"micros\":6},\"user_activities\",{\"micros\":3},\"played_application_ids\",{\"micros\":3},\"linked_users\",{\"micros\":2},\"ad_personalization_toggles_disabled\",{\"micros\":3},\"regional_feature_config\",{\"micros\":2}]}]},\"starting_guild_connect\",{\"micros\":32,\"calls\":[]},\"presence_started\",{\"micros\":307,\"calls\":[]},\"guilds_started\",{\"micros\":70,\"calls\":[]},\"lobbies_started\",{\"micros\":1,\"calls\":[]},\"guilds_connect\",{\"micros\":1,\"calls\":[]},\"presence_connect\",{\"micros\":13941,\"calls\":[]},\"connect_finished\",{\"micros\":13957,\"calls\":[]},\"build_ready\",{\"micros\":14,\"calls\":[]},\"clean_ready\",{\"micros\":0,\"calls\":[]},\"optimize_ready\",{\"micros\":1,\"calls\":[]},\"split_ready\",{\"micros\":42,\"calls\":[]}]}]}]"]}
+[2026-02-14T17:34:33.722205+00:00] DiscordPHP.DEBUG: client created and session id stored {"session_id":"482756b966dea44fe65ffe101b18b3f6","user":{"id":"1471909193886859294","username":"AsepSahur","discriminator":"6954","global_name":null,"avatar":"https://cdn.discordapp.com/avatars/1471909193886859294/8a88b0710fa41f7eef469c3dedc30e27.webp?size=1024","bot":true,"system":null,"mfa_enabled":false,"banner":null,"accent_color":null,"locale":null,"verified":true,"email":null,"flags":0,"premium_type":null,"public_flags":null,"avatar_decoration_data":null,"collectibles":null,"primary_guild":null}}
+[2026-02-14T17:34:33.728414+00:00] DiscordPHP.INFO: stored guilds {"count":0,"unavailable":1}
+[2026-02-14T17:34:34.222915+00:00] DiscordPHP.DEBUG: guild available {"guild":"1428530728706117632","unavailable":1}
+[2026-02-14T17:34:34.226546+00:00] DiscordPHP.INFO: all guilds are now available {"count":1}
+[2026-02-14T17:34:34.226691+00:00] DiscordPHP.INFO: loadAllMembers option is disabled, not setting chunking up
+[2026-02-14T17:34:34.228423+00:00] DiscordPHP.INFO: voice class initialized
+[2026-02-14T17:34:34.228507+00:00] DiscordPHP.INFO: client is ready
+[2026-02-14T17:34:34.228549+00:00] DiscordPHP.INFO: The 'ready' event is deprecated and will be removed in a future version of DiscordPHP. Please use 'init' instead.
+Bot is ready!
+[2026-02-14T17:34:34.483250+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.483360+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:34.485450+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.486602+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.487055+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.487593+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.487713+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.488476+00:00] DiscordPHP.DEBUG: BUCKET postapplications/:application_id/commands queued REQ POST applications/1471909193886859294/commands
+[2026-02-14T17:34:34.714902+00:00] DiscordPHP.INFO: received session id for voice session {"guild":"1428530728706117632","session_id":"482756b966dea44fe65ffe101b18b3f6"}
+[2026-02-14T17:34:34.717132+00:00] DiscordPHP.INFO: received token and endpoint for voice session {"guild":"1428530728706117632","token":"*****","endpoint":"c-fra20-5f509e9b.discord.media:2083"}
+[2026-02-14T17:34:34.733765+00:00] DiscordPHP.DEBUG: Creating new voice websocket {"endpoint":"c-fra20-5f509e9b.discord.media:2083"}
+[2026-02-14T17:34:38.231445+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:38.235940+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.238993+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.463643+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:38.463750+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.463838+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.715045+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:38.715179+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.715294+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.963521+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:38.963648+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:38.963740+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:39.215534+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:39.215661+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:39.215738+00:00] DiscordPHP.INFO: BUCKET postapplications/:application_id/commands expecting rate limit, timer interval 18007 ms
+[2026-02-14T17:34:39.216420+00:00] DiscordPHP.DEBUG: connected to voice websocket
+[2026-02-14T17:34:39.218555+00:00] DiscordPHP.DEBUG: sending identify {"packet":{"op":0,"d":{"server_id":"1428530728706117632","user_id":"1471909193886859294","token":"*****","max_dave_protocol_version":0,"session_id":"482756b966dea44fe65ffe101b18b3f6"}}}
+[2026-02-14T17:34:39.219880+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:34:39.347543+00:00] DiscordPHP.DEBUG: received voice ready packet {"data":{"streams":[{"type":"video","ssrc":1935,"rtx_ssrc":1936,"rid":"","quality":0,"active":false}],"ssrc":1934,"port":19314,"modes":["aead_aes256_gcm_rtpsize","aead_xchacha20_poly1305_rtpsize"],"ip":"104.29.147.190","experiments":["fixed_keyframe_interval"]}}
+[2026-02-14T17:34:39.464642+00:00] DiscordPHP.ERROR: error while connecting to udp {"e":"syntax error, unexpected identifier \"SILENCE_FRAME\", expecting \"=\""}
+[2026-02-14T17:34:39.464778+00:00] DiscordPHP.ERROR: error initializing voice manager {"e":"syntax error, unexpected identifier \"SILENCE_FRAME\", expecting \"=\""}
+Could not join voice channel: syntax error, unexpected identifier "SILENCE_FRAME", expecting "="
+[2026-02-14T17:34:39.466740+00:00] DiscordPHP.ERROR: error initializing voice client {"e":"syntax error, unexpected identifier \"SILENCE_FRAME\", expecting \"=\""}
+[2026-02-14T17:34:39.466928+00:00] DiscordPHP.DEBUG: received client connect packet {"data":{"Discord\\WebSockets\\Payload":{"op":11,"d":{"user_ids":["235088799074484224","830530156048285716","906246223504240641","923944350612848700","1414108278354608278"]}}}}
+[2026-02-14T17:34:39.469249+00:00] DiscordPHP.DEBUG: received speaking packet {"data":{"user_id":"235088799074484224","ssrc":228,"speaking":1}}
+[2026-02-14T17:34:39.469398+00:00] DiscordPHP.DEBUG: received speaking packet {"data":{"user_id":"830530156048285716","ssrc":243,"speaking":1}}
+[2026-02-14T17:34:39.470418+00:00] DiscordPHP.DEBUG: received flags packet {"data":{"attributes":{"user_id":"235088799074484224","flags":null},"created":true,"class":"Discord\\Voice\\Flags"}}
+[2026-02-14T17:34:39.470613+00:00] DiscordPHP.DEBUG: received flags packet {"data":{"attributes":{"user_id":"830530156048285716","flags":null},"created":true,"class":"Discord\\Voice\\Flags"}}
+[2026-02-14T17:34:39.470724+00:00] DiscordPHP.DEBUG: received flags packet {"data":{"attributes":{"user_id":"906246223504240641","flags":null},"created":true,"class":"Discord\\Voice\\Flags"}}
+[2026-02-14T17:34:39.470787+00:00] DiscordPHP.DEBUG: received flags packet {"data":{"attributes":{"user_id":"923944350612848700","flags":2},"created":true,"class":"Discord\\Voice\\Flags"}}
+[2026-02-14T17:34:39.470833+00:00] DiscordPHP.DEBUG: received flags packet {"data":{"attributes":{"user_id":"1414108278354608278","flags":2},"created":true,"class":"Discord\\Voice\\Flags"}}
+[2026-02-14T17:34:39.471638+00:00] DiscordPHP.DEBUG: received platform packet {"data":{"attributes":{"user_id":"235088799074484224","platform":null},"created":true,"class":"Discord\\Voice\\Platform"}}
+[2026-02-14T17:34:39.471761+00:00] DiscordPHP.DEBUG: received platform packet {"data":{"attributes":{"user_id":"830530156048285716","platform":null},"created":true,"class":"Discord\\Voice\\Platform"}}
+[2026-02-14T17:34:39.471828+00:00] DiscordPHP.DEBUG: received platform packet {"data":{"attributes":{"user_id":"906246223504240641","platform":null},"created":true,"class":"Discord\\Voice\\Platform"}}
+[2026-02-14T17:34:39.471874+00:00] DiscordPHP.DEBUG: received platform packet {"data":{"attributes":{"user_id":"923944350612848700","platform":1},"created":true,"class":"Discord\\Voice\\Platform"}}
+[2026-02-14T17:34:39.471994+00:00] DiscordPHP.DEBUG: received platform packet {"data":{"attributes":{"user_id":"1414108278354608278","platform":1},"created":true,"class":"Discord\\Voice\\Platform"}}
+[2026-02-14T17:34:39.472040+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":472.03588485717773}
+[2026-02-14T17:34:39.521773+00:00] DiscordPHP.INFO: received session id for voice session {"guild":"1428530728706117632","session_id":"482756b966dea44fe65ffe101b18b3f6"}
+[2026-02-14T17:34:39.714513+00:00] DiscordPHP.WARNING: voice websocket closed {"op":4014,"reason":"Disconnected."}
+[2026-02-14T17:34:39.714739+00:00] DiscordPHP.WARNING: received critical opcode - not reconnecting {"op":4014,"reason":"Disconnected."}
+[2026-02-14T17:34:52.979398+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:34:57.225711+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:57.516904+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:57.517062+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:57.517159+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:57.614652+00:00] DiscordPHP.DEBUG: REQ POST applications/1471909193886859294/commands successful
+[2026-02-14T17:34:57.614778+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:34:59.731602+00:00] DiscordPHP.DEBUG: BUCKET postinteractions/:interaction_id/:interaction_token/callback queued REQ POST interactions/1472285008109764903/aW50ZXJhY3Rpb246MTQ3MjI4NTAwODEwOTc2NDkwMzp5ek81V2d5T1dmNzhDbjVoTVNmU1pmd0NLdnE0QjZ2ZDdCU2hIc21pUU9jc1hVcFFXT3I0YzVzRjlwdFBKTldIdjNrYmptYUNvMlNkNmRsYXN0WEVYRWNNOTZ6NVZMcmJmS2ZnVnc0SFdWa3JISVBDcTJSN21JYW5uQ2gwclBocA/callback
+[2026-02-14T17:34:59.731754+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":0,"empty":true}
+Could not join voice channel: You cannot join more than one voice channel per guild/server.
+[2026-02-14T17:34:59.735903+00:00] DiscordPHP.DEBUG: BUCKET patchwebhooks/:application_id/:interaction_token/messages/@original queued REQ PATCH webhooks/1471909193886859294/aW50ZXJhY3Rpb246MTQ3MjI4NTAwODEwOTc2NDkwMzp5ek81V2d5T1dmNzhDbjVoTVNmU1pmd0NLdnE0QjZ2ZDdCU2hIc21pUU9jc1hVcFFXT3I0YzVzRjlwdFBKTldIdjNrYmptYUNvMlNkNmRsYXN0WEVYRWNNOTZ6NVZMcmJmS2ZnVnc0SFdWa3JISVBDcTJSN21JYW5uQ2gwclBocA/messages/@original
+[2026-02-14T17:34:59.735998+00:00] DiscordPHP.DEBUG: http not checking interaction queue {"waiting":1,"empty":true}
+[2026-02-14T17:35:01.231064+00:00] DiscordPHP.DEBUG: REQ POST interactions/1472285008109764903/aW50ZXJhY3Rpb246MTQ3MjI4NTAwODEwOTc2NDkwMzp5ek81V2d5T1dmNzhDbjVoTVNmU1pmd0NLdnE0QjZ2ZDdCU2hIc21pUU9jc1hVcFFXT3I0YzVzRjlwdFBKTldIdjNrYmptYUNvMlNkNmRsYXN0WEVYRWNNOTZ6NVZMcmJmS2ZnVnc0SFdWa3JISVBDcTJSN21JYW5uQ2gwclBocA/callback successful
+[2026-02-14T17:35:01.231183+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":1,"empty":true}
+[2026-02-14T17:35:01.467159+00:00] DiscordPHP.DEBUG: REQ PATCH webhooks/1471909193886859294/aW50ZXJhY3Rpb246MTQ3MjI4NTAwODEwOTc2NDkwMzp5ek81V2d5T1dmNzhDbjVoTVNmU1pmd0NLdnE0QjZ2ZDdCU2hIc21pUU9jc1hVcFFXT3I0YzVzRjlwdFBKTldIdjNrYmptYUNvMlNkNmRsYXN0WEVYRWNNOTZ6NVZMcmJmS2ZnVnc0SFdWa3JISVBDcTJSN21JYW5uQ2gwclBocA/messages/@original successful
+[2026-02-14T17:35:01.467302+00:00] DiscordPHP.DEBUG: http not checking queue {"waiting":0,"empty":true}
+[2026-02-14T17:35:06.730579+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:35:14.726139+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":8}
+[2026-02-14T17:35:14.773734+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":47.34015464782715}
+[2026-02-14T17:35:20.486026+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:35:33.249495+00:00] DiscordPHP.DEBUG: resetting payload count {"count":5}
+[2026-02-14T17:35:34.236641+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:35:47.988865+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:35:55.978998+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":8}
+[2026-02-14T17:35:56.020302+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":41.02015495300293}
+[2026-02-14T17:36:01.739956+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:36:15.492364+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:36:29.249215+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:36:33.250107+00:00] DiscordPHP.DEBUG: resetting payload count {"count":1}
+[2026-02-14T17:36:37.230128+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":11}
+[2026-02-14T17:36:37.286064+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":55.63092231750488}
+[2026-02-14T17:36:43.000574+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:36:56.764775+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:37:10.525293+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:37:18.485572+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":11}
+[2026-02-14T17:37:18.525100+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":39.26587104797363}
+[2026-02-14T17:37:24.280203+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:37:33.259032+00:00] DiscordPHP.DEBUG: resetting payload count {"count":2}
+[2026-02-14T17:37:38.031284+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:37:51.789903+00:00] DiscordPHP.DEBUG: sending heartbeat
+[2026-02-14T17:37:59.736211+00:00] DiscordPHP.DEBUG: sending heartbeat {"seq":11}
+[2026-02-14T17:37:59.776711+00:00] DiscordPHP.DEBUG: received heartbeat ack {"response_time":40.16280174255371}
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8e69394
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "team-reflex/discord-php": "^10.45"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..f9c4dbd
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,2719 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "b8a5c711a7f7eb8cbe767d09d82bee68",
+ "packages": [
+ {
+ "name": "carbonphp/carbon-doctrine-types",
+ "version": "3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "conflict": {
+ "doctrine/dbal": "<4.0.0 || >=5.0.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^4.0.0",
+ "nesbot/carbon": "^2.71.0 || ^3.0.0",
+ "phpunit/phpunit": "^10.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "KyleKatarn",
+ "email": "kylekatarnls@gmail.com"
+ }
+ ],
+ "description": "Types to use Carbon in Doctrine",
+ "keywords": [
+ "carbon",
+ "date",
+ "datetime",
+ "doctrine",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
+ "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-02-09T16:56:22+00:00"
+ },
+ {
+ "name": "discord-php-helpers/collection",
+ "version": "v8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/Collection.git",
+ "reference": "27eae375a21d5086a665b803881cee09863b2269"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/Collection/zipball/27eae375a21d5086a665b803881cee09863b2269",
+ "reference": "27eae375a21d5086a665b803881cee09863b2269",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "team-reflex/discord-php": "dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Valithor Obsidion",
+ "email": "valzargaming@gmail.com"
+ }
+ ],
+ "description": "A collection library for DiscordPHP. Inspired by Laravel Collections.",
+ "support": {
+ "issues": "https://github.com/discord-php/Collection/issues",
+ "source": "https://github.com/discord-php/Collection/tree/v8.0.0"
+ },
+ "time": "2025-12-26T05:22:34+00:00"
+ },
+ {
+ "name": "discord-php-helpers/voice",
+ "version": "v8.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP-Voice.git",
+ "reference": "9c6ce3547f1463a2726d6726fd971ea26f99a1dc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP-Voice/zipball/9c6ce3547f1463a2726d6726fd971ea26f99a1dc",
+ "reference": "9c6ce3547f1463a2726d6726fd971ea26f99a1dc",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1.2"
+ },
+ "require-dev": {
+ "ext-ffi": "*",
+ "friendsofphp/php-cs-fixer": "^3",
+ "team-reflex/discord-php": "dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Alexandre Candeias",
+ "email": "alexandreluisbarreto@gmail.com"
+ }
+ ],
+ "description": "A voice library for DiscordPHP",
+ "support": {
+ "chat": "https://discord.gg/dphp",
+ "docs": "https://discord-php.github.io/DiscordPHP/",
+ "issues": "https://github.com/discord-php/DiscordPHP-Voice/issues",
+ "source": "https://github.com/discord-php/DiscordPHP-Voice/tree/v8.0.5",
+ "wiki": "https://github.com/discord-php/DiscordPHP/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Log1x",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/valzargaming",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/DiscordPHP",
+ "type": "patreon"
+ }
+ ],
+ "time": "2026-01-15T17:34:15+00:00"
+ },
+ {
+ "name": "discord-php/http",
+ "version": "v10.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP-Http.git",
+ "reference": "f2d281a435ec4f42b084dd1b843cfac91ac2fbbd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP-Http/zipball/f2d281a435ec4f42b084dd1b843cfac91ac2fbbd",
+ "reference": "f2d281a435ec4f42b084dd1b843cfac91ac2fbbd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "react/http": "^1.2",
+ "react/promise": "^2.2 || ^3.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "mockery/mockery": "^1.5",
+ "monolog/monolog": "^2.2",
+ "phpunit/phpunit": "^9.5",
+ "psy/psysh": "^0.10.6",
+ "react/async": "^4 || ^3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "For alternative to ReactPHP/Http Browser"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Discord\\Http\\": "src/Discord",
+ "Tests\\Discord\\Http\\": "tests/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ }
+ ],
+ "description": "Handles HTTP requests to Discord servers",
+ "support": {
+ "issues": "https://github.com/discord-php/DiscordPHP-Http/issues",
+ "source": "https://github.com/discord-php/DiscordPHP-Http/tree/v10.9.0"
+ },
+ "time": "2026-01-13T20:26:33+00:00"
+ },
+ {
+ "name": "discord/interactions",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord/discord-interactions-php.git",
+ "reference": "a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord/discord-interactions-php/zipball/a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457",
+ "reference": "a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457",
+ "shasum": ""
+ },
+ "conflict": {
+ "simplito/elliptic-php": "<1.0,>=1.1"
+ },
+ "require-dev": {
+ "simplito/elliptic-php": "^1.0"
+ },
+ "suggest": {
+ "simplito/elliptic-php": "Required to validate interaction signatures."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ian Webster",
+ "email": "ianw_php@ianww.com"
+ }
+ ],
+ "description": "Utils for implementing the Discord Interactions API",
+ "keywords": [
+ "discord"
+ ],
+ "support": {
+ "issues": "https://github.com/discord/discord-interactions-php/issues",
+ "source": "https://github.com/discord/discord-interactions-php/tree/2.2.0"
+ },
+ "time": "2022-02-09T17:58:51+00:00"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9 || ^6"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Evenement\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ],
+ "support": {
+ "issues": "https://github.com/igorw/evenement/issues",
+ "source": "https://github.com/igorw/evenement/tree/v3.0.2"
+ },
+ "time": "2023-08-08T05:53:35+00:00"
+ },
+ {
+ "name": "fig/http-message-util",
+ "version": "1.1.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message-util.git",
+ "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765",
+ "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "suggest": {
+ "psr/http-message": "The package containing the PSR-7 interfaces"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Fig\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/http-message-util/issues",
+ "source": "https://github.com/php-fig/http-message-util/tree/1.1.5"
+ },
+ "time": "2020-11-24T22:02:12+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "21dc724a0583619cd1652f673303492272778051"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-08-23T21:21:41+00:00"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "3.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "ext-json": "*",
+ "graylog2/gelf-php": "^1.4.2 || ^2.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8 || ^2.0",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+ "predis/predis": "^1.1 || ^2",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "https://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/monolog/issues",
+ "source": "https://github.com/Seldaek/monolog/tree/3.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-02T08:56:05+00:00"
+ },
+ {
+ "name": "nesbot/carbon",
+ "version": "3.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon.git",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f",
+ "shasum": ""
+ },
+ "require": {
+ "carbonphp/carbon-doctrine-types": "<100.0",
+ "ext-json": "*",
+ "php": "^8.1",
+ "psr/clock": "^1.0",
+ "symfony/clock": "^6.3.12 || ^7.0 || ^8.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^3.6.3 || ^4.0",
+ "doctrine/orm": "^2.15.2 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^v3.87.1",
+ "kylekatarnls/multi-tester": "^2.5.3",
+ "phpmd/phpmd": "^2.15.0",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^2.1.22",
+ "phpunit/phpunit": "^10.5.53",
+ "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
+ },
+ "bin": [
+ "bin/carbon"
+ ],
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Carbon\\Laravel\\ServiceProvider"
+ ]
+ },
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-2.x": "2.x-dev",
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Carbon\\": "src/Carbon/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Nesbitt",
+ "email": "brian@nesbot.com",
+ "homepage": "https://markido.com"
+ },
+ {
+ "name": "kylekatarnls",
+ "homepage": "https://github.com/kylekatarnls"
+ }
+ ],
+ "description": "An API extension for DateTime that supports 281 different languages.",
+ "homepage": "https://carbonphp.github.io/carbon/",
+ "keywords": [
+ "date",
+ "datetime",
+ "time"
+ ],
+ "support": {
+ "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
+ "issues": "https://github.com/CarbonPHP/carbon/issues",
+ "source": "https://github.com/CarbonPHP/carbon"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon#sponsor",
+ "type": "opencollective"
+ },
+ {
+ "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-29T09:26:29+00:00"
+ },
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Clock\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for reading the clock.",
+ "homepage": "https://github.com/php-fig/clock",
+ "keywords": [
+ "clock",
+ "now",
+ "psr",
+ "psr-20",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/clock/issues",
+ "source": "https://github.com/php-fig/clock/tree/1.0.0"
+ },
+ "time": "2022-11-25T14:36:26+00:00"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "time": "2024-04-15T12:06:14+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
+ },
+ "time": "2023-04-04T09:50:52+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "time": "2024-09-11T13:17:53+00:00"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+ },
+ "time": "2021-10-29T13:26:27+00:00"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
+ "name": "ratchet/pawl",
+ "version": "v0.4.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ratchetphp/Pawl.git",
+ "reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/2c582373c78271de32cb04c755c4c0db7e09c9c0",
+ "reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0",
+ "guzzlehttp/psr7": "^2.0",
+ "php": ">=7.4",
+ "ratchet/rfc6455": "^0.3.1 || ^0.4.0",
+ "react/socket": "^1.9"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
+ },
+ "suggest": {
+ "reactivex/rxphp": "~2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "Ratchet\\Client\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Asynchronous WebSocket client",
+ "keywords": [
+ "Ratchet",
+ "async",
+ "client",
+ "websocket",
+ "websocket client"
+ ],
+ "support": {
+ "issues": "https://github.com/ratchetphp/Pawl/issues",
+ "source": "https://github.com/ratchetphp/Pawl/tree/v0.4.3"
+ },
+ "time": "2025-03-19T16:47:38+00:00"
+ },
+ {
+ "name": "ratchet/rfc6455",
+ "version": "v0.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ratchetphp/RFC6455.git",
+ "reference": "859d95f85dda0912c6d5b936d036d044e3af47ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/859d95f85dda0912c6d5b936d036d044e3af47ef",
+ "reference": "859d95f85dda0912c6d5b936d036d044e3af47ef",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4",
+ "psr/http-factory-implementation": "^1.0",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "require-dev": {
+ "guzzlehttp/psr7": "^2.7",
+ "phpunit/phpunit": "^9.5",
+ "react/socket": "^1.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Ratchet\\RFC6455\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Matt Bonneau",
+ "role": "Developer"
+ }
+ ],
+ "description": "RFC6455 WebSocket protocol handler",
+ "homepage": "http://socketo.me",
+ "keywords": [
+ "WebSockets",
+ "rfc6455",
+ "websocket"
+ ],
+ "support": {
+ "chat": "https://gitter.im/reactphp/reactphp",
+ "issues": "https://github.com/ratchetphp/RFC6455/issues",
+ "source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.0"
+ },
+ "time": "2025-02-24T01:18:22+00:00"
+ },
+ {
+ "name": "react/async",
+ "version": "v4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/async.git",
+ "reference": "635d50e30844a484495713e8cb8d9e079c0008a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5",
+ "reference": "635d50e30844a484495713e8cb8d9e079c0008a5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.8 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "1.10.39",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "React\\Async\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async utilities and fibers for ReactPHP",
+ "keywords": [
+ "async",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/async/issues",
+ "source": "https://github.com/reactphp/async/tree/v4.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-06-04T14:40:02+00:00"
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "^3.0 || ^2.0 || ^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/cache/issues",
+ "source": "https://github.com/reactphp/cache/tree/v1.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2022-11-30T15:59:55+00:00"
+ },
+ {
+ "name": "react/child-process",
+ "version": "v0.6.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/child-process.git",
+ "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3",
+ "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/socket": "^1.16",
+ "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\ChildProcess\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven library for executing child processes with ReactPHP.",
+ "keywords": [
+ "event-driven",
+ "process",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/child-process/issues",
+ "source": "https://github.com/reactphp/child-process/tree/v0.6.7"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-12-23T15:25:20+00:00"
+ },
+ {
+ "name": "react/datagram",
+ "version": "v1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/datagram.git",
+ "reference": "9236e1f5a67a6029be17d551e9858c487836c301"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/datagram/zipball/9236e1f5a67a6029be17d551e9858c487836c301",
+ "reference": "9236e1f5a67a6029be17d551e9858c487836c301",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3",
+ "react/dns": "^1.13",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.1 || ^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3 || ^2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Datagram\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven UDP datagram socket client and server for ReactPHP",
+ "homepage": "https://github.com/reactphp/datagram",
+ "keywords": [
+ "Socket",
+ "async",
+ "client",
+ "datagram",
+ "dgram",
+ "reactphp",
+ "server",
+ "udp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/datagram/issues",
+ "source": "https://github.com/reactphp/datagram/tree/v1.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-09-06T11:22:35+00:00"
+ },
+ {
+ "name": "react/dns",
+ "version": "v1.14.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
+ "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.7 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3 || ^2",
+ "react/promise-timer": "^1.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/dns/issues",
+ "source": "https://github.com/reactphp/dns/tree/v1.14.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-11-18T19:34:28+00:00"
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+ "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "suggest": {
+ "ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/event-loop/issues",
+ "source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-11-17T20:46:25+00:00"
+ },
+ {
+ "name": "react/http",
+ "version": "v1.11.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/http.git",
+ "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/http/zipball/8db02de41dcca82037367f67a2d4be365b1c4db9",
+ "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "fig/http-message-util": "^1.1",
+ "php": ">=5.3.0",
+ "psr/http-message": "^1.0",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.3 || ^1.2.1",
+ "react/socket": "^1.16",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "clue/http-proxy-react": "^1.8",
+ "clue/reactphp-ssh-proxy": "^1.4",
+ "clue/socks-react": "^1.4",
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.2 || ^3 || ^2",
+ "react/promise-stream": "^1.4",
+ "react/promise-timer": "^1.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Http\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP",
+ "keywords": [
+ "async",
+ "client",
+ "event-driven",
+ "http",
+ "http client",
+ "http server",
+ "https",
+ "psr-7",
+ "reactphp",
+ "server",
+ "streaming"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/http/issues",
+ "source": "https://github.com/reactphp/http/tree/v1.11.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-11-20T15:24:08+00:00"
+ },
+ {
+ "name": "react/promise",
+ "version": "v3.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
+ "reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "1.12.28 || 1.4.10",
+ "phpunit/phpunit": "^9.6 || ^7.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/promise/issues",
+ "source": "https://github.com/reactphp/promise/tree/v3.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-08-19T18:57:03+00:00"
+ },
+ {
+ "name": "react/socket",
+ "version": "v1.17.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
+ "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^1.13",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.6 || ^1.2.1",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3.3 || ^2",
+ "react/promise-stream": "^1.4",
+ "react/promise-timer": "^1.11"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/socket/issues",
+ "source": "https://github.com/reactphp/socket/tree/v1.17.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2025-11-19T20:47:34+00:00"
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.2"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/stream/issues",
+ "source": "https://github.com/reactphp/stream/tree/v1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2024-06-11T12:45:25+00:00"
+ },
+ {
+ "name": "symfony/clock",
+ "version": "v7.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/clock.git",
+ "reference": "9169f24776edde469914c1e7a1442a50f7a4e110"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110",
+ "reference": "9169f24776edde469914c1e7a1442a50f7a4e110",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/clock": "^1.0",
+ "symfony/polyfill-php83": "^1.28"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/now.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Clock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Decouples applications from the system clock",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clock",
+ "psr20",
+ "time"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/clock/tree/v7.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-11-12T15:39:26+00:00"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:21:43+00:00"
+ },
+ {
+ "name": "symfony/options-resolver",
+ "version": "v7.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/options-resolver.git",
+ "reference": "b38026df55197f9e39a44f3215788edf83187b80"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80",
+ "reference": "b38026df55197f9e39a44f3215788edf83187b80",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\OptionsResolver\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an improved replacement for the array_replace PHP function",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "config",
+ "configuration",
+ "options"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/options-resolver/tree/v7.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-11-12T15:39:26+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-12-23T08:48:59+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-02T08:10:11+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php83",
+ "version": "v1.33.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php83.git",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php83\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-08T02:45:35+00:00"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v7.4.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "bfde13711f53f549e73b06d27b35a55207528877"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877",
+ "reference": "bfde13711f53f549e73b06d27b35a55207528877",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^2.5.3|^3.3"
+ },
+ "conflict": {
+ "nikic/php-parser": "<5.0",
+ "symfony/config": "<6.4",
+ "symfony/console": "<6.4",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<6.4",
+ "symfony/service-contracts": "<2.5",
+ "symfony/twig-bundle": "<6.4",
+ "symfony/yaml": "<6.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3|3.0"
+ },
+ "require-dev": {
+ "nikic/php-parser": "^5.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/console": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/finder": "^6.4|^7.0|^8.0",
+ "symfony/http-client-contracts": "^2.5|^3.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/intl": "^6.4|^7.0|^8.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/routing": "^6.4|^7.0|^8.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^6.4|^7.0|^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to internationalize your application",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/translation/tree/v7.4.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2026-01-13T10:40:19+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v3.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-07-15T13:41:35+00:00"
+ },
+ {
+ "name": "team-reflex/discord-php",
+ "version": "v10.45.18",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP.git",
+ "reference": "05a2b31b1301827e26a478c9e970da103a20bd67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP/zipball/05a2b31b1301827e26a478c9e970da103a20bd67",
+ "reference": "05a2b31b1301827e26a478c9e970da103a20bd67",
+ "shasum": ""
+ },
+ "require": {
+ "discord-php-helpers/collection": "^8.0",
+ "discord-php-helpers/voice": "^8.0 || dev-main",
+ "discord-php/http": "^10.1.7",
+ "discord/interactions": "^2.2",
+ "ext-zlib": "*",
+ "monolog/monolog": "^2.1.1 || ^3.0",
+ "nesbot/carbon": "^2.38 || ^3.0",
+ "php": "^8.1.2",
+ "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
+ "ratchet/pawl": "^0.4.3",
+ "react/async": "^4.0 || ^3.0",
+ "react/cache": "^0.5 || ^0.6 || ^1.0",
+ "react/child-process": "^0.6.3",
+ "react/datagram": "^1.8",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0.0",
+ "symfony/options-resolver": "^5.1.11 || ^6.0 || ^7.0",
+ "trafficcophp/bytebuffer": "^0.3"
+ },
+ "require-dev": {
+ "carthage-software/mago": "1.1.0",
+ "davidcole1340/reactsh": "dev-master",
+ "friendsofphp/php-cs-fixer": "^3",
+ "laravel/pint": "^1.21",
+ "pestphp/pest": "^4.1.2",
+ "symfony/cache": "^5.4",
+ "symfony/var-dumper": "*",
+ "wyrihaximus/react-cache-redis": "^4.5"
+ },
+ "suggest": {
+ "ext-ev": "For a faster, and more performant loop.",
+ "ext-event": "For a faster, and more performant loop.",
+ "ext-fileinfo": "For function mime_content_type().",
+ "ext-gmp": "For 64 bit calculations on x86 (32 bit) PHP.",
+ "ext-mbstring": "For accurate calculations of string length when handling non-english characters.",
+ "ext-uv": "For a faster, and more performant loop. Preferred.",
+ "laracord/laracord": "Provides Laracord integration for DiscordPHP."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Discord/functions.php"
+ ],
+ "psr-4": {
+ "Discord\\": "src/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Valithor Obsidion",
+ "email": "valithor@discordphp.org"
+ }
+ ],
+ "description": "An unofficial API to interact with the voice and text service Discord.",
+ "support": {
+ "chat": "https://discord.gg/dphp",
+ "docs": "https://discord-php.github.io/DiscordPHP/",
+ "issues": "https://github.com/discord-php/DiscordPHP/issues",
+ "source": "https://github.com/discord-php/DiscordPHP/tree/v10.45.18",
+ "wiki": "https://github.com/discord-php/DiscordPHP/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://valgorithms.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/valithor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/valzargaming",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/DiscordPHP",
+ "type": "patreon"
+ }
+ ],
+ "time": "2026-02-11T16:00:11+00:00"
+ },
+ {
+ "name": "trafficcophp/bytebuffer",
+ "version": "v0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nesQuick/ByteBuffer.git",
+ "reference": "e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nesQuick/ByteBuffer/zipball/e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4",
+ "reference": "e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "TrafficCophp": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ole 'nesQuick' Michaelis",
+ "email": "ole.michaelis@googlemail.com",
+ "homepage": "http://www.codestars.eu"
+ }
+ ],
+ "description": "Node.js inspired byte stream buffer for PHP.",
+ "keywords": [
+ "Buffer",
+ "Bytehandling",
+ "Socket",
+ "binary data",
+ "library",
+ "pack",
+ "stream",
+ "wrapper"
+ ],
+ "support": {
+ "issues": "https://github.com/nesQuick/ByteBuffer/issues",
+ "source": "https://github.com/nesQuick/ByteBuffer/tree/master"
+ },
+ "time": "2016-01-13T22:50:58+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
+}
diff --git a/index.php b/index.php
index 7205f3d..e2fa5f6 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,149 @@
-
-
-
-
-
- New Style
-query("SELECT setting_key, setting_value FROM bot_settings")->fetchAll(PDO::FETCH_KEY_PAIR);
+$logs = $db->query("SELECT * FROM bot_logs ORDER BY created_at DESC LIMIT 10")->fetchAll();
+
+$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Discord Sahur Bot Management Dashboard';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Sahur Bot Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
Sahur Bot
+
+
+ = strtoupper($settings['bot_status'] ?? 'offline') ?>
+
+
-
-
- Page updated: = htmlspecialchars($now) ?> (UTC)
-
+
+
+
+
+
+
+
+
+
+
+
+
+
Manage the Discord bot service status.
+
+ Start Bot
+ Stop Bot
+ Restart
+
+
+
+
+
+
+
+
+
+
+
+
+
No activity logs found.
+
+
+
+
+ = $log['created_at'] ?>
+ = $log['log_level'] ?>
+
+
= htmlspecialchars($log['message']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/manage_bot.php b/manage_bot.php
new file mode 100644
index 0000000..be61c8b
--- /dev/null
+++ b/manage_bot.php
@@ -0,0 +1,73 @@
+ false];
+
+function isBotRunning($pidFile) {
+ if (!file_exists($pidFile)) return false;
+ $pid = (int)file_get_contents($pidFile);
+ return posix_getpgid($pid) !== false;
+}
+
+if ($action === 'start') {
+ if (isBotRunning($pidFile)) {
+ $response['error'] = 'Bot is already running.';
+ } else {
+ $logFile = __DIR__ . '/bot_output.log';
+ $cmd = "nohup php $botScript > $logFile 2>&1 & echo $!";
+ $pid = trim(shell_exec($cmd));
+ if ($pid) {
+ file_put_contents($pidFile, $pid);
+ $response['success'] = true;
+ // Log to DB
+ $db = db();
+ $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES ('Bot started manually', 'info')")->execute();
+ } else {
+ $response['error'] = 'Failed to start bot.';
+ }
+ }
+} elseif ($action === 'stop') {
+ if (file_exists($pidFile)) {
+ $pid = (int)file_get_contents($pidFile);
+ exec("kill $pid");
+ unlink($pidFile);
+ $response['success'] = true;
+ // Update status in DB
+ $db = db();
+ $db->prepare("UPDATE bot_settings SET setting_value = 'offline' WHERE setting_key = 'bot_status'")->execute();
+ $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES ('Bot stopped manually', 'info')")->execute();
+ } else {
+ $response['error'] = 'Bot is not running.';
+ }
+} elseif ($action === 'restart') {
+ // Stop
+ if (file_exists($pidFile)) {
+ $pid = (int)file_get_contents($pidFile);
+ exec("kill $pid");
+ unlink($pidFile);
+ }
+ // Start
+ $logFile = __DIR__ . '/bot_output.log';
+ $cmd = "nohup php $botScript > $logFile 2>&1 & echo $!";
+ $pid = trim(shell_exec($cmd));
+ if ($pid) {
+ file_put_contents($pidFile, $pid);
+ $response['success'] = true;
+ $db = db();
+ $db->prepare("UPDATE bot_settings SET setting_value = 'online' WHERE setting_key = 'bot_status'")->execute();
+ $db->prepare("INSERT INTO bot_logs (message, log_level) VALUES ('Bot restarted manually', 'info')")->execute();
+ } else {
+ $response['error'] = 'Failed to restart bot.';
+ }
+} else {
+ $response['error'] = 'Invalid action.';
+}
+
+echo json_encode($response);
diff --git a/save_config.php b/save_config.php
new file mode 100644
index 0000000..759d82e
--- /dev/null
+++ b/save_config.php
@@ -0,0 +1,31 @@
+ false, 'error' => 'Invalid request method']);
+ exit;
+}
+
+$token = $_POST['bot_token'] ?? '';
+$vc_id = $_POST['voice_channel_id'] ?? '';
+$time = $_POST['sahur_time'] ?? '';
+$source = $_POST['sahur_source'] ?? '';
+
+try {
+ $db = db();
+ $stmt = $db->prepare("UPDATE bot_settings SET setting_value = ? WHERE setting_key = ?");
+
+ $stmt->execute([$token, 'bot_token']);
+ $stmt->execute([$vc_id, 'voice_channel_id']);
+ $stmt->execute([$time, 'sahur_time']);
+ $stmt->execute([$source, 'sahur_source']);
+
+ // Log the change
+ $logStmt = $db->prepare("INSERT INTO bot_logs (log_level, message) VALUES ('info', 'Settings updated by administrator')");
+ $logStmt->execute();
+
+ echo json_encode(['success' => true]);
+} catch (Exception $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+}
diff --git a/test_sahur.php b/test_sahur.php
new file mode 100644
index 0000000..4e6b7b8
--- /dev/null
+++ b/test_sahur.php
@@ -0,0 +1,18 @@
+query("SELECT setting_value FROM bot_settings WHERE setting_key = 'voice_channel_id'")->fetchColumn();
+ $source = $db->query("SELECT setting_value FROM bot_settings WHERE setting_key = 'sahur_source'")->fetchColumn();
+
+ $message = "Test Sahur triggered. Joining VC ID: $vc_id and playing from source: $source";
+
+ $stmt = $db->prepare("INSERT INTO bot_logs (log_level, message) VALUES ('info', ?)");
+ $stmt->execute([$message]);
+
+ echo json_encode(['success' => true]);
+} catch (Exception $e) {
+ echo json_encode(['success' => false, 'error' => $e->getMessage()]);
+}
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 0000000..184a748
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,25 @@
+realpath = realpath($opened_path) ?: $opened_path;
+ $opened_path = $this->realpath;
+ $this->handle = fopen($this->realpath, $mode);
+ $this->position = 0;
+
+ return (bool) $this->handle;
+ }
+
+ public function stream_read($count)
+ {
+ $data = fread($this->handle, $count);
+
+ if ($this->position === 0) {
+ $data = preg_replace('{^#!.*\r?\n}', '', $data);
+ }
+
+ $this->position += strlen($data);
+
+ return $data;
+ }
+
+ public function stream_cast($castAs)
+ {
+ return $this->handle;
+ }
+
+ public function stream_close()
+ {
+ fclose($this->handle);
+ }
+
+ public function stream_lock($operation)
+ {
+ return $operation ? flock($this->handle, $operation) : true;
+ }
+
+ public function stream_seek($offset, $whence)
+ {
+ if (0 === fseek($this->handle, $offset, $whence)) {
+ $this->position = ftell($this->handle);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function stream_tell()
+ {
+ return $this->position;
+ }
+
+ public function stream_eof()
+ {
+ return feof($this->handle);
+ }
+
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ public function stream_set_option($option, $arg1, $arg2)
+ {
+ return true;
+ }
+
+ public function url_stat($path, $flags)
+ {
+ $path = substr($path, 17);
+ if (file_exists($path)) {
+ return stat($path);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (
+ (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+ || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+ ) {
+ include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
+ exit(0);
+ }
+}
+
+include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
diff --git a/vendor/carbonphp/carbon-doctrine-types/LICENSE b/vendor/carbonphp/carbon-doctrine-types/LICENSE
new file mode 100644
index 0000000..2ee1671
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Carbon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/carbonphp/carbon-doctrine-types/README.md b/vendor/carbonphp/carbon-doctrine-types/README.md
new file mode 100644
index 0000000..5a18121
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/README.md
@@ -0,0 +1,14 @@
+# carbonphp/carbon-doctrine-types
+
+Types to use Carbon in Doctrine
+
+## Documentation
+
+[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
+
+This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
+from `nestbot/carbon` package.
+
+Externalization allows to better deal with different versions of dbal. With
+version 4.0 of dbal, it no longer sustainable to be compatible with all version
+using a single code.
diff --git a/vendor/carbonphp/carbon-doctrine-types/composer.json b/vendor/carbonphp/carbon-doctrine-types/composer.json
new file mode 100644
index 0000000..abf45c5
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/composer.json
@@ -0,0 +1,36 @@
+{
+ "name": "carbonphp/carbon-doctrine-types",
+ "description": "Types to use Carbon in Doctrine",
+ "type": "library",
+ "keywords": [
+ "date",
+ "time",
+ "DateTime",
+ "Carbon",
+ "Doctrine"
+ ],
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^4.0.0",
+ "nesbot/carbon": "^2.71.0 || ^3.0.0",
+ "phpunit/phpunit": "^10.3"
+ },
+ "conflict": {
+ "doctrine/dbal": "<4.0.0 || >=5.0.0"
+ },
+ "license": "MIT",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+ }
+ },
+ "authors": [
+ {
+ "name": "KyleKatarn",
+ "email": "kylekatarnls@gmail.com"
+ }
+ ],
+ "minimum-stability": "dev"
+}
diff --git a/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php
new file mode 100644
index 0000000..a63a9b8
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/CarbonDoctrineType.php
@@ -0,0 +1,16 @@
+
+ */
+ protected function getCarbonClassName(): string
+ {
+ return Carbon::class;
+ }
+
+ public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
+ {
+ $precision = min(
+ $fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(),
+ $this->getMaximumPrecision($platform),
+ );
+
+ $type = parent::getSQLDeclaration($fieldDeclaration, $platform);
+
+ if (!$precision) {
+ return $type;
+ }
+
+ if (str_contains($type, '(')) {
+ return preg_replace('/\(\d+\)/', "($precision)", $type);
+ }
+
+ [$before, $after] = explode(' ', "$type ");
+
+ return trim("$before($precision) $after");
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
+ {
+ if ($value === null) {
+ return $value;
+ }
+
+ if ($value instanceof DateTimeInterface) {
+ return $value->format('Y-m-d H:i:s.u');
+ }
+
+ throw InvalidType::new(
+ $value,
+ static::class,
+ ['null', 'DateTime', 'Carbon']
+ );
+ }
+
+ private function doConvertToPHPValue(mixed $value)
+ {
+ $class = $this->getCarbonClassName();
+
+ if ($value === null || is_a($value, $class)) {
+ return $value;
+ }
+
+ if ($value instanceof DateTimeInterface) {
+ return $class::instance($value);
+ }
+
+ $date = null;
+ $error = null;
+
+ try {
+ $date = $class::parse($value);
+ } catch (Exception $exception) {
+ $error = $exception;
+ }
+
+ if (!$date) {
+ throw ValueNotConvertible::new(
+ $value,
+ static::class,
+ 'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()',
+ $error
+ );
+ }
+
+ return $date;
+ }
+
+ private function getMaximumPrecision(AbstractPlatform $platform): int
+ {
+ if ($platform instanceof DB2Platform) {
+ return 12;
+ }
+
+ if ($platform instanceof OraclePlatform) {
+ return 9;
+ }
+
+ if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) {
+ return 3;
+ }
+
+ return 6;
+ }
+}
diff --git a/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php
new file mode 100644
index 0000000..cd9896f
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeDefaultPrecision.php
@@ -0,0 +1,30 @@
+ */
+ use CarbonTypeConverter;
+
+ /**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable
+ {
+ return $this->doConvertToPHPValue($value);
+ }
+
+ /**
+ * @return class-string
+ */
+ protected function getCarbonClassName(): string
+ {
+ return CarbonImmutable::class;
+ }
+}
diff --git a/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php
new file mode 100644
index 0000000..89e4b79
--- /dev/null
+++ b/vendor/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine/DateTimeType.php
@@ -0,0 +1,24 @@
+ */
+ use CarbonTypeConverter;
+
+ /**
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon
+ {
+ return $this->doConvertToPHPValue($value);
+ }
+}
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 0000000..a72151c
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,585 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var \Closure(string):void */
+ private static $includeFile;
+
+ /** @var ?string */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var string[]
+ * @psalm-var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var bool[]
+ * @psalm-var array
+ */
+ private $missingClasses = array();
+
+ /** @var ?string */
+ private $apcuPrefix;
+
+ /**
+ * @var self[]
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param ?string $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ self::initializeIncludeClosure();
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return string[] Array of classname => path
+ * @psalm-return array
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param string[] $classMap Class to filename map
+ * @psalm-param array $classMap
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ $includeFile = self::$includeFile;
+ $includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
+ *
+ * @return self[]
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return void
+ */
+ private static function initializeIncludeClosure()
+ {
+ if (self::$includeFile !== null) {
+ return;
+ }
+
+ /**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ */
+ self::$includeFile = \Closure::bind(static function($file) {
+ include $file;
+ }, null, null);
+ }
+}
diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000..51e734a
--- /dev/null
+++ b/vendor/composer/InstalledVersions.php
@@ -0,0 +1,359 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints((string) $constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require $vendorDir.'/composer/installed.php';
+ $installed[] = self::$installedByVendor[$vendorDir] = $required;
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */
+ $required = require __DIR__ . '/installed.php';
+ self::$installed = $required;
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ if (self::$installed !== array()) {
+ $installed[] = self::$installed;
+ }
+
+ return $installed;
+ }
+}
diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE
new file mode 100644
index 0000000..62ecfd8
--- /dev/null
+++ b/vendor/composer/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000..4d558d0
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,26 @@
+ $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'DateError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateError.php',
+ 'DateException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateException.php',
+ 'DateInvalidOperationException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php',
+ 'DateInvalidTimeZoneException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php',
+ 'DateMalformedIntervalStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php',
+ 'DateMalformedPeriodStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php',
+ 'DateMalformedStringException' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php',
+ 'DateObjectError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php',
+ 'DateRangeError' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php',
+ 'Override' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/Override.php',
+ 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+ 'SQLite3Exception' => $vendorDir . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
+ 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+ 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
new file mode 100644
index 0000000..8e077c1
--- /dev/null
+++ b/vendor/composer/autoload_files.php
@@ -0,0 +1,20 @@
+ $vendorDir . '/react/promise/src/functions_include.php',
+ '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
+ '662a729f963d39afe703c9d9b7ab4a8c' => $vendorDir . '/symfony/polyfill-php83/bootstrap.php',
+ '2203a247e6fda86070a5e4e07aed533a' => $vendorDir . '/symfony/clock/Resources/now.php',
+ 'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
+ '3be16222a6efa6dd226a219eaaff823b' => $vendorDir . '/ratchet/pawl/src/functions_include.php',
+ 'c4e03ecd470d2a87804979c0a8152284' => $vendorDir . '/react/async/src/functions_include.php',
+ '864b292aadc96fda0e2642b894a38d16' => $vendorDir . '/team-reflex/discord-php/src/Discord/functions.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000..47622f2
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,10 @@
+ array($vendorDir . '/trafficcophp/bytebuffer/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 0000000..f9d0104
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,41 @@
+ array($vendorDir . '/discord-php/http/tests/Discord'),
+ 'Symfony\\Polyfill\\Php83\\' => array($vendorDir . '/symfony/polyfill-php83'),
+ 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
+ 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
+ 'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
+ 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
+ 'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'),
+ 'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),
+ 'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
+ 'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
+ 'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
+ 'React\\Http\\' => array($vendorDir . '/react/http/src'),
+ 'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
+ 'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
+ 'React\\Datagram\\' => array($vendorDir . '/react/datagram/src'),
+ 'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'),
+ 'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
+ 'React\\Async\\' => array($vendorDir . '/react/async/src'),
+ 'Ratchet\\RFC6455\\' => array($vendorDir . '/ratchet/rfc6455/src'),
+ 'Ratchet\\Client\\' => array($vendorDir . '/ratchet/pawl/src'),
+ 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
+ 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
+ 'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
+ 'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
+ 'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'),
+ 'Evenement\\' => array($vendorDir . '/evenement/evenement/src'),
+ 'Discord\\Http\\' => array($vendorDir . '/discord-php/http/src/Discord'),
+ 'Discord\\' => array($vendorDir . '/discord/interactions/discord', $vendorDir . '/discord-php-helpers/voice/src/Discord', $vendorDir . '/discord-php-helpers/collection/src/Discord', $vendorDir . '/team-reflex/discord-php/src/Discord'),
+ 'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
+ 'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 0000000..bf18b1e
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,50 @@
+register(true);
+
+ $filesToLoad = \Composer\Autoload\ComposerStaticInit3f15e11d83f664c614d99889af2eb047::$files;
+ $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+ require $file;
+ }
+ }, null, null);
+ foreach ($filesToLoad as $fileIdentifier => $file) {
+ $requireFile($fileIdentifier, $file);
+ }
+
+ return $loader;
+ }
+}
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
new file mode 100644
index 0000000..49d7572
--- /dev/null
+++ b/vendor/composer/autoload_static.php
@@ -0,0 +1,263 @@
+ __DIR__ . '/..' . '/react/promise/src/functions_include.php',
+ '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
+ '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
+ 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
+ '662a729f963d39afe703c9d9b7ab4a8c' => __DIR__ . '/..' . '/symfony/polyfill-php83/bootstrap.php',
+ '2203a247e6fda86070a5e4e07aed533a' => __DIR__ . '/..' . '/symfony/clock/Resources/now.php',
+ 'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
+ '3be16222a6efa6dd226a219eaaff823b' => __DIR__ . '/..' . '/ratchet/pawl/src/functions_include.php',
+ 'c4e03ecd470d2a87804979c0a8152284' => __DIR__ . '/..' . '/react/async/src/functions_include.php',
+ '864b292aadc96fda0e2642b894a38d16' => __DIR__ . '/..' . '/team-reflex/discord-php/src/Discord/functions.php',
+ );
+
+ public static $prefixLengthsPsr4 = array (
+ 'T' =>
+ array (
+ 'Tests\\Discord\\Http\\' => 19,
+ ),
+ 'S' =>
+ array (
+ 'Symfony\\Polyfill\\Php83\\' => 23,
+ 'Symfony\\Polyfill\\Php80\\' => 23,
+ 'Symfony\\Polyfill\\Mbstring\\' => 26,
+ 'Symfony\\Contracts\\Translation\\' => 30,
+ 'Symfony\\Component\\Translation\\' => 30,
+ 'Symfony\\Component\\OptionsResolver\\' => 34,
+ 'Symfony\\Component\\Clock\\' => 24,
+ ),
+ 'R' =>
+ array (
+ 'React\\Stream\\' => 13,
+ 'React\\Socket\\' => 13,
+ 'React\\Promise\\' => 14,
+ 'React\\Http\\' => 11,
+ 'React\\EventLoop\\' => 16,
+ 'React\\Dns\\' => 10,
+ 'React\\Datagram\\' => 15,
+ 'React\\ChildProcess\\' => 19,
+ 'React\\Cache\\' => 12,
+ 'React\\Async\\' => 12,
+ 'Ratchet\\RFC6455\\' => 16,
+ 'Ratchet\\Client\\' => 15,
+ ),
+ 'P' =>
+ array (
+ 'Psr\\SimpleCache\\' => 16,
+ 'Psr\\Log\\' => 8,
+ 'Psr\\Http\\Message\\' => 17,
+ 'Psr\\Clock\\' => 10,
+ ),
+ 'M' =>
+ array (
+ 'Monolog\\' => 8,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
+ ),
+ 'F' =>
+ array (
+ 'Fig\\Http\\Message\\' => 17,
+ ),
+ 'E' =>
+ array (
+ 'Evenement\\' => 10,
+ ),
+ 'D' =>
+ array (
+ 'Discord\\Http\\' => 13,
+ 'Discord\\' => 8,
+ ),
+ 'C' =>
+ array (
+ 'Carbon\\Doctrine\\' => 16,
+ 'Carbon\\' => 7,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Tests\\Discord\\Http\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/discord-php/http/tests/Discord',
+ ),
+ 'Symfony\\Polyfill\\Php83\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php83',
+ ),
+ 'Symfony\\Polyfill\\Php80\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
+ ),
+ 'Symfony\\Polyfill\\Mbstring\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+ ),
+ 'Symfony\\Contracts\\Translation\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/translation-contracts',
+ ),
+ 'Symfony\\Component\\Translation\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/translation',
+ ),
+ 'Symfony\\Component\\OptionsResolver\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/options-resolver',
+ ),
+ 'Symfony\\Component\\Clock\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/clock',
+ ),
+ 'React\\Stream\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/stream/src',
+ ),
+ 'React\\Socket\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/socket/src',
+ ),
+ 'React\\Promise\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/promise/src',
+ ),
+ 'React\\Http\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/http/src',
+ ),
+ 'React\\EventLoop\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/event-loop/src',
+ ),
+ 'React\\Dns\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/dns/src',
+ ),
+ 'React\\Datagram\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/datagram/src',
+ ),
+ 'React\\ChildProcess\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/child-process/src',
+ ),
+ 'React\\Cache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/cache/src',
+ ),
+ 'React\\Async\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/react/async/src',
+ ),
+ 'Ratchet\\RFC6455\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ratchet/rfc6455/src',
+ ),
+ 'Ratchet\\Client\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/ratchet/pawl/src',
+ ),
+ 'Psr\\SimpleCache\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/simple-cache/src',
+ ),
+ 'Psr\\Log\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/log/src',
+ ),
+ 'Psr\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/http-message/src',
+ 1 => __DIR__ . '/..' . '/psr/http-factory/src',
+ ),
+ 'Psr\\Clock\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/psr/clock/src',
+ ),
+ 'Monolog\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
+ 'Fig\\Http\\Message\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/fig/http-message-util/src',
+ ),
+ 'Evenement\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/evenement/evenement/src',
+ ),
+ 'Discord\\Http\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/discord-php/http/src/Discord',
+ ),
+ 'Discord\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/discord/interactions/discord',
+ 1 => __DIR__ . '/..' . '/discord-php-helpers/voice/src/Discord',
+ 2 => __DIR__ . '/..' . '/discord-php-helpers/collection/src/Discord',
+ 3 => __DIR__ . '/..' . '/team-reflex/discord-php/src/Discord',
+ ),
+ 'Carbon\\Doctrine\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine',
+ ),
+ 'Carbon\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
+ ),
+ );
+
+ public static $prefixesPsr0 = array (
+ 'T' =>
+ array (
+ 'TrafficCophp' =>
+ array (
+ 0 => __DIR__ . '/..' . '/trafficcophp/bytebuffer/src',
+ ),
+ ),
+ );
+
+ public static $classMap = array (
+ 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'DateError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateError.php',
+ 'DateException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateException.php',
+ 'DateInvalidOperationException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidOperationException.php',
+ 'DateInvalidTimeZoneException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateInvalidTimeZoneException.php',
+ 'DateMalformedIntervalStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedIntervalStringException.php',
+ 'DateMalformedPeriodStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedPeriodStringException.php',
+ 'DateMalformedStringException' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateMalformedStringException.php',
+ 'DateObjectError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateObjectError.php',
+ 'DateRangeError' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/DateRangeError.php',
+ 'Override' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/Override.php',
+ 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+ 'SQLite3Exception' => __DIR__ . '/..' . '/symfony/polyfill-php83/Resources/stubs/SQLite3Exception.php',
+ 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+ 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+ 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit3f15e11d83f664c614d99889af2eb047::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit3f15e11d83f664c614d99889af2eb047::$prefixDirsPsr4;
+ $loader->prefixesPsr0 = ComposerStaticInit3f15e11d83f664c614d99889af2eb047::$prefixesPsr0;
+ $loader->classMap = ComposerStaticInit3f15e11d83f664c614d99889af2eb047::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 0000000..248b221
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,2820 @@
+{
+ "packages": [
+ {
+ "name": "carbonphp/carbon-doctrine-types",
+ "version": "3.2.0",
+ "version_normalized": "3.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "conflict": {
+ "doctrine/dbal": "<4.0.0 || >=5.0.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^4.0.0",
+ "nesbot/carbon": "^2.71.0 || ^3.0.0",
+ "phpunit/phpunit": "^10.3"
+ },
+ "time": "2024-02-09T16:56:22+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "KyleKatarn",
+ "email": "kylekatarnls@gmail.com"
+ }
+ ],
+ "description": "Types to use Carbon in Doctrine",
+ "keywords": [
+ "carbon",
+ "date",
+ "datetime",
+ "doctrine",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues",
+ "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon",
+ "type": "open_collective"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../carbonphp/carbon-doctrine-types"
+ },
+ {
+ "name": "discord-php-helpers/collection",
+ "version": "v8.0.0",
+ "version_normalized": "8.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/Collection.git",
+ "reference": "27eae375a21d5086a665b803881cee09863b2269"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/Collection/zipball/27eae375a21d5086a665b803881cee09863b2269",
+ "reference": "27eae375a21d5086a665b803881cee09863b2269",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "team-reflex/discord-php": "dev-master"
+ },
+ "time": "2025-12-26T05:22:34+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Valithor Obsidion",
+ "email": "valzargaming@gmail.com"
+ }
+ ],
+ "description": "A collection library for DiscordPHP. Inspired by Laravel Collections.",
+ "support": {
+ "issues": "https://github.com/discord-php/Collection/issues",
+ "source": "https://github.com/discord-php/Collection/tree/v8.0.0"
+ },
+ "install-path": "../discord-php-helpers/collection"
+ },
+ {
+ "name": "discord-php-helpers/voice",
+ "version": "v8.0.5",
+ "version_normalized": "8.0.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP-Voice.git",
+ "reference": "9c6ce3547f1463a2726d6726fd971ea26f99a1dc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP-Voice/zipball/9c6ce3547f1463a2726d6726fd971ea26f99a1dc",
+ "reference": "9c6ce3547f1463a2726d6726fd971ea26f99a1dc",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1.2"
+ },
+ "require-dev": {
+ "ext-ffi": "*",
+ "friendsofphp/php-cs-fixer": "^3",
+ "team-reflex/discord-php": "dev-master"
+ },
+ "time": "2026-01-15T17:34:15+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Alexandre Candeias",
+ "email": "alexandreluisbarreto@gmail.com"
+ }
+ ],
+ "description": "A voice library for DiscordPHP",
+ "support": {
+ "chat": "https://discord.gg/dphp",
+ "docs": "https://discord-php.github.io/DiscordPHP/",
+ "issues": "https://github.com/discord-php/DiscordPHP-Voice/issues",
+ "source": "https://github.com/discord-php/DiscordPHP-Voice/tree/v8.0.5",
+ "wiki": "https://github.com/discord-php/DiscordPHP/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Log1x",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/valzargaming",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/DiscordPHP",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../discord-php-helpers/voice"
+ },
+ {
+ "name": "discord-php/http",
+ "version": "v10.9.0",
+ "version_normalized": "10.9.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP-Http.git",
+ "reference": "f2d281a435ec4f42b084dd1b843cfac91ac2fbbd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP-Http/zipball/f2d281a435ec4f42b084dd1b843cfac91ac2fbbd",
+ "reference": "f2d281a435ec4f42b084dd1b843cfac91ac2fbbd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "react/http": "^1.2",
+ "react/promise": "^2.2 || ^3.0.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "mockery/mockery": "^1.5",
+ "monolog/monolog": "^2.2",
+ "phpunit/phpunit": "^9.5",
+ "psy/psysh": "^0.10.6",
+ "react/async": "^4 || ^3"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "For alternative to ReactPHP/Http Browser"
+ },
+ "time": "2026-01-13T20:26:33+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Discord\\Http\\": "src/Discord",
+ "Tests\\Discord\\Http\\": "tests/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ }
+ ],
+ "description": "Handles HTTP requests to Discord servers",
+ "support": {
+ "issues": "https://github.com/discord-php/DiscordPHP-Http/issues",
+ "source": "https://github.com/discord-php/DiscordPHP-Http/tree/v10.9.0"
+ },
+ "install-path": "../discord-php/http"
+ },
+ {
+ "name": "discord/interactions",
+ "version": "2.2.0",
+ "version_normalized": "2.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord/discord-interactions-php.git",
+ "reference": "a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord/discord-interactions-php/zipball/a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457",
+ "reference": "a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457",
+ "shasum": ""
+ },
+ "conflict": {
+ "simplito/elliptic-php": "<1.0,>=1.1"
+ },
+ "require-dev": {
+ "simplito/elliptic-php": "^1.0"
+ },
+ "suggest": {
+ "simplito/elliptic-php": "Required to validate interaction signatures."
+ },
+ "time": "2022-02-09T17:58:51+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ian Webster",
+ "email": "ianw_php@ianww.com"
+ }
+ ],
+ "description": "Utils for implementing the Discord Interactions API",
+ "keywords": [
+ "discord"
+ ],
+ "support": {
+ "issues": "https://github.com/discord/discord-interactions-php/issues",
+ "source": "https://github.com/discord/discord-interactions-php/tree/2.2.0"
+ },
+ "install-path": "../discord/interactions"
+ },
+ {
+ "name": "evenement/evenement",
+ "version": "v3.0.2",
+ "version_normalized": "3.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/igorw/evenement.git",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9 || ^6"
+ },
+ "time": "2023-08-08T05:53:35+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Evenement\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": [
+ "event-dispatcher",
+ "event-emitter"
+ ],
+ "support": {
+ "issues": "https://github.com/igorw/evenement/issues",
+ "source": "https://github.com/igorw/evenement/tree/v3.0.2"
+ },
+ "install-path": "../evenement/evenement"
+ },
+ {
+ "name": "fig/http-message-util",
+ "version": "1.1.5",
+ "version_normalized": "1.1.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message-util.git",
+ "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765",
+ "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "suggest": {
+ "psr/http-message": "The package containing the PSR-7 interfaces"
+ },
+ "time": "2020-11-24T22:02:12+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Fig\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/http-message-util/issues",
+ "source": "https://github.com/php-fig/http-message-util/tree/1.1.5"
+ },
+ "install-path": "../fig/http-message-util"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "2.8.0",
+ "version_normalized": "2.8.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "21dc724a0583619cd1652f673303492272778051"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2025-08-23T21:21:41+00:00",
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.8.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../guzzlehttp/psr7"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "3.10.0",
+ "version_normalized": "3.10.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^2.0 || ^3.0"
+ },
+ "provide": {
+ "psr/log-implementation": "3.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "ext-json": "*",
+ "graylog2/gelf-php": "^1.4.2 || ^2.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8 || ^2.0",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+ "predis/predis": "^1.1 || ^2",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "ext-openssl": "Required to send log messages using SSL",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "time": "2026-01-02T08:56:05+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "https://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "support": {
+ "issues": "https://github.com/Seldaek/monolog/issues",
+ "source": "https://github.com/Seldaek/monolog/tree/3.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Seldaek",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../monolog/monolog"
+ },
+ {
+ "name": "nesbot/carbon",
+ "version": "3.11.1",
+ "version_normalized": "3.11.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon.git",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/f438fcc98f92babee98381d399c65336f3a3827f",
+ "reference": "f438fcc98f92babee98381d399c65336f3a3827f",
+ "shasum": ""
+ },
+ "require": {
+ "carbonphp/carbon-doctrine-types": "<100.0",
+ "ext-json": "*",
+ "php": "^8.1",
+ "psr/clock": "^1.0",
+ "symfony/clock": "^6.3.12 || ^7.0 || ^8.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^3.6.3 || ^4.0",
+ "doctrine/orm": "^2.15.2 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^v3.87.1",
+ "kylekatarnls/multi-tester": "^2.5.3",
+ "phpmd/phpmd": "^2.15.0",
+ "phpstan/extension-installer": "^1.4.3",
+ "phpstan/phpstan": "^2.1.22",
+ "phpunit/phpunit": "^10.5.53",
+ "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0"
+ },
+ "time": "2026-01-29T09:26:29+00:00",
+ "bin": [
+ "bin/carbon"
+ ],
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Carbon\\Laravel\\ServiceProvider"
+ ]
+ },
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-2.x": "2.x-dev",
+ "dev-master": "3.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\": "src/Carbon/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Nesbitt",
+ "email": "brian@nesbot.com",
+ "homepage": "https://markido.com"
+ },
+ {
+ "name": "kylekatarnls",
+ "homepage": "https://github.com/kylekatarnls"
+ }
+ ],
+ "description": "An API extension for DateTime that supports 281 different languages.",
+ "homepage": "https://carbonphp.github.io/carbon/",
+ "keywords": [
+ "date",
+ "datetime",
+ "time"
+ ],
+ "support": {
+ "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html",
+ "issues": "https://github.com/CarbonPHP/carbon/issues",
+ "source": "https://github.com/CarbonPHP/carbon"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon#sponsor",
+ "type": "opencollective"
+ },
+ {
+ "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../nesbot/carbon"
+ },
+ {
+ "name": "psr/clock",
+ "version": "1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/clock.git",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0"
+ },
+ "time": "2022-11-25T14:36:26+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Clock\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for reading the clock.",
+ "homepage": "https://github.com/php-fig/clock",
+ "keywords": [
+ "clock",
+ "now",
+ "psr",
+ "psr-20",
+ "time"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/clock/issues",
+ "source": "https://github.com/php-fig/clock/tree/1.0.0"
+ },
+ "install-path": "../psr/clock"
+ },
+ {
+ "name": "psr/http-factory",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-factory.git",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "psr/http-message": "^1.0 || ^2.0"
+ },
+ "time": "2024-04-15T12:06:14+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "psr",
+ "psr-17",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-factory"
+ },
+ "install-path": "../psr/http-factory"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.1",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "time": "2023-04-04T09:50:52+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/http-message/tree/1.1"
+ },
+ "install-path": "../psr/http-message"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.2",
+ "version_normalized": "3.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "time": "2024-09-11T13:17:53+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.2"
+ },
+ "install-path": "../psr/log"
+ },
+ {
+ "name": "psr/simple-cache",
+ "version": "3.0.0",
+ "version_normalized": "3.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/simple-cache.git",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "time": "2021-10-29T13:26:27+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Psr\\SimpleCache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interfaces for simple caching",
+ "keywords": [
+ "cache",
+ "caching",
+ "psr",
+ "psr-16",
+ "simple-cache"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/simple-cache/tree/3.0.0"
+ },
+ "install-path": "../psr/simple-cache"
+ },
+ {
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "install-path": "../ralouphie/getallheaders"
+ },
+ {
+ "name": "ratchet/pawl",
+ "version": "v0.4.3",
+ "version_normalized": "0.4.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ratchetphp/Pawl.git",
+ "reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ratchetphp/Pawl/zipball/2c582373c78271de32cb04c755c4c0db7e09c9c0",
+ "reference": "2c582373c78271de32cb04c755c4c0db7e09c9c0",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0",
+ "guzzlehttp/psr7": "^2.0",
+ "php": ">=7.4",
+ "ratchet/rfc6455": "^0.3.1 || ^0.4.0",
+ "react/socket": "^1.9"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8"
+ },
+ "suggest": {
+ "reactivex/rxphp": "~2.0"
+ },
+ "time": "2025-03-19T16:47:38+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "Ratchet\\Client\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Asynchronous WebSocket client",
+ "keywords": [
+ "Ratchet",
+ "async",
+ "client",
+ "websocket",
+ "websocket client"
+ ],
+ "support": {
+ "issues": "https://github.com/ratchetphp/Pawl/issues",
+ "source": "https://github.com/ratchetphp/Pawl/tree/v0.4.3"
+ },
+ "install-path": "../ratchet/pawl"
+ },
+ {
+ "name": "ratchet/rfc6455",
+ "version": "v0.4.0",
+ "version_normalized": "0.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ratchetphp/RFC6455.git",
+ "reference": "859d95f85dda0912c6d5b936d036d044e3af47ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ratchetphp/RFC6455/zipball/859d95f85dda0912c6d5b936d036d044e3af47ef",
+ "reference": "859d95f85dda0912c6d5b936d036d044e3af47ef",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4",
+ "psr/http-factory-implementation": "^1.0",
+ "symfony/polyfill-php80": "^1.15"
+ },
+ "require-dev": {
+ "guzzlehttp/psr7": "^2.7",
+ "phpunit/phpunit": "^9.5",
+ "react/socket": "^1.3"
+ },
+ "time": "2025-02-24T01:18:22+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Ratchet\\RFC6455\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "role": "Developer"
+ },
+ {
+ "name": "Matt Bonneau",
+ "role": "Developer"
+ }
+ ],
+ "description": "RFC6455 WebSocket protocol handler",
+ "homepage": "http://socketo.me",
+ "keywords": [
+ "WebSockets",
+ "rfc6455",
+ "websocket"
+ ],
+ "support": {
+ "chat": "https://gitter.im/reactphp/reactphp",
+ "issues": "https://github.com/ratchetphp/RFC6455/issues",
+ "source": "https://github.com/ratchetphp/RFC6455/tree/v0.4.0"
+ },
+ "install-path": "../ratchet/rfc6455"
+ },
+ {
+ "name": "react/async",
+ "version": "v4.3.0",
+ "version_normalized": "4.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/async.git",
+ "reference": "635d50e30844a484495713e8cb8d9e079c0008a5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/async/zipball/635d50e30844a484495713e8cb8d9e079c0008a5",
+ "reference": "635d50e30844a484495713e8cb8d9e079c0008a5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.8 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "1.10.39",
+ "phpunit/phpunit": "^9.6"
+ },
+ "time": "2024-06-04T14:40:02+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "React\\Async\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async utilities and fibers for ReactPHP",
+ "keywords": [
+ "async",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/async/issues",
+ "source": "https://github.com/reactphp/async/tree/v4.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/async"
+ },
+ {
+ "name": "react/cache",
+ "version": "v1.2.0",
+ "version_normalized": "1.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/cache.git",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b",
+ "reference": "d47c472b64aa5608225f47965a484b75c7817d5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/promise": "^3.0 || ^2.0 || ^1.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35"
+ },
+ "time": "2022-11-30T15:59:55+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Cache\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, Promise-based cache interface for ReactPHP",
+ "keywords": [
+ "cache",
+ "caching",
+ "promise",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/cache/issues",
+ "source": "https://github.com/reactphp/cache/tree/v1.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/cache"
+ },
+ {
+ "name": "react/child-process",
+ "version": "v0.6.7",
+ "version_normalized": "0.6.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/child-process.git",
+ "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3",
+ "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/socket": "^1.16",
+ "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0"
+ },
+ "time": "2025-12-23T15:25:20+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\ChildProcess\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven library for executing child processes with ReactPHP.",
+ "keywords": [
+ "event-driven",
+ "process",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/child-process/issues",
+ "source": "https://github.com/reactphp/child-process/tree/v0.6.7"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/child-process"
+ },
+ {
+ "name": "react/datagram",
+ "version": "v1.10.0",
+ "version_normalized": "1.10.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/datagram.git",
+ "reference": "9236e1f5a67a6029be17d551e9858c487836c301"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/datagram/zipball/9236e1f5a67a6029be17d551e9858c487836c301",
+ "reference": "9236e1f5a67a6029be17d551e9858c487836c301",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3",
+ "react/dns": "^1.13",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.1 || ^1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3 || ^2"
+ },
+ "time": "2024-09-06T11:22:35+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Datagram\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven UDP datagram socket client and server for ReactPHP",
+ "homepage": "https://github.com/reactphp/datagram",
+ "keywords": [
+ "Socket",
+ "async",
+ "client",
+ "datagram",
+ "dgram",
+ "reactphp",
+ "server",
+ "udp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/datagram/issues",
+ "source": "https://github.com/reactphp/datagram/tree/v1.10.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/datagram"
+ },
+ {
+ "name": "react/dns",
+ "version": "v1.14.0",
+ "version_normalized": "1.14.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/dns.git",
+ "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3",
+ "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "react/cache": "^1.0 || ^0.6 || ^0.5",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.7 || ^1.2.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3 || ^2",
+ "react/promise-timer": "^1.11"
+ },
+ "time": "2025-11-18T19:34:28+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Dns\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async DNS resolver for ReactPHP",
+ "keywords": [
+ "async",
+ "dns",
+ "dns-resolver",
+ "reactphp"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/dns/issues",
+ "source": "https://github.com/reactphp/dns/tree/v1.14.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/dns"
+ },
+ {
+ "name": "react/event-loop",
+ "version": "v1.6.0",
+ "version_normalized": "1.6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/event-loop.git",
+ "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+ "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "suggest": {
+ "ext-pcntl": "For signal handling support when using the StreamSelectLoop"
+ },
+ "time": "2025-11-17T20:46:25+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\EventLoop\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.",
+ "keywords": [
+ "asynchronous",
+ "event-loop"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/event-loop/issues",
+ "source": "https://github.com/reactphp/event-loop/tree/v1.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/event-loop"
+ },
+ {
+ "name": "react/http",
+ "version": "v1.11.0",
+ "version_normalized": "1.11.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/http.git",
+ "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/http/zipball/8db02de41dcca82037367f67a2d4be365b1c4db9",
+ "reference": "8db02de41dcca82037367f67a2d4be365b1c4db9",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "fig/http-message-util": "^1.1",
+ "php": ">=5.3.0",
+ "psr/http-message": "^1.0",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.3 || ^1.2.1",
+ "react/socket": "^1.16",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "clue/http-proxy-react": "^1.8",
+ "clue/reactphp-ssh-proxy": "^1.4",
+ "clue/socks-react": "^1.4",
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.2 || ^3 || ^2",
+ "react/promise-stream": "^1.4",
+ "react/promise-timer": "^1.11"
+ },
+ "time": "2024-11-20T15:24:08+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Http\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP",
+ "keywords": [
+ "async",
+ "client",
+ "event-driven",
+ "http",
+ "http client",
+ "http server",
+ "https",
+ "psr-7",
+ "reactphp",
+ "server",
+ "streaming"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/http/issues",
+ "source": "https://github.com/reactphp/http/tree/v1.11.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/http"
+ },
+ {
+ "name": "react/promise",
+ "version": "v3.3.0",
+ "version_normalized": "3.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
+ "reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "1.12.28 || 1.4.10",
+ "phpunit/phpunit": "^9.6 || ^7.5"
+ },
+ "time": "2025-08-19T18:57:03+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "keywords": [
+ "promise",
+ "promises"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/promise/issues",
+ "source": "https://github.com/reactphp/promise/tree/v3.3.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/promise"
+ },
+ {
+ "name": "react/socket",
+ "version": "v1.17.0",
+ "version_normalized": "1.17.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/socket.git",
+ "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08",
+ "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.0",
+ "react/dns": "^1.13",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.2 || ^2.6 || ^1.2.1",
+ "react/stream": "^1.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
+ "react/async": "^4.3 || ^3.3 || ^2",
+ "react/promise-stream": "^1.4",
+ "react/promise-timer": "^1.11"
+ },
+ "time": "2025-11-19T20:47:34+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Socket\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
+ "keywords": [
+ "Connection",
+ "Socket",
+ "async",
+ "reactphp",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/socket/issues",
+ "source": "https://github.com/reactphp/socket/tree/v1.17.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/socket"
+ },
+ {
+ "name": "react/stream",
+ "version": "v1.4.0",
+ "version_normalized": "1.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/stream.git",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d",
+ "shasum": ""
+ },
+ "require": {
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "php": ">=5.3.8",
+ "react/event-loop": "^1.2"
+ },
+ "require-dev": {
+ "clue/stream-filter": "~1.2",
+ "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
+ },
+ "time": "2024-06-11T12:45:25+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "React\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering",
+ "homepage": "https://clue.engineering/"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "reactphp@ceesjankiewiet.nl",
+ "homepage": "https://wyrihaximus.net/"
+ },
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com",
+ "homepage": "https://sorgalla.com/"
+ },
+ {
+ "name": "Chris Boden",
+ "email": "cboden@gmail.com",
+ "homepage": "https://cboden.dev/"
+ }
+ ],
+ "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP",
+ "keywords": [
+ "event-driven",
+ "io",
+ "non-blocking",
+ "pipe",
+ "reactphp",
+ "readable",
+ "stream",
+ "writable"
+ ],
+ "support": {
+ "issues": "https://github.com/reactphp/stream/issues",
+ "source": "https://github.com/reactphp/stream/tree/v1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/reactphp",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../react/stream"
+ },
+ {
+ "name": "symfony/clock",
+ "version": "v7.4.0",
+ "version_normalized": "7.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/clock.git",
+ "reference": "9169f24776edde469914c1e7a1442a50f7a4e110"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110",
+ "reference": "9169f24776edde469914c1e7a1442a50f7a4e110",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "psr/clock": "^1.0",
+ "symfony/polyfill-php83": "^1.28"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "time": "2025-11-12T15:39:26+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "Resources/now.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Clock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Decouples applications from the system clock",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clock",
+ "psr20",
+ "time"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/clock/tree/v7.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/clock"
+ },
+ {
+ "name": "symfony/deprecation-contracts",
+ "version": "v3.6.0",
+ "version_normalized": "3.6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/deprecation-contracts.git",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "time": "2024-09-25T14:21:43+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/deprecation-contracts"
+ },
+ {
+ "name": "symfony/options-resolver",
+ "version": "v7.4.0",
+ "version_normalized": "7.4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/options-resolver.git",
+ "reference": "b38026df55197f9e39a44f3215788edf83187b80"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b38026df55197f9e39a44f3215788edf83187b80",
+ "reference": "b38026df55197f9e39a44f3215788edf83187b80",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "time": "2025-11-12T15:39:26+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\OptionsResolver\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an improved replacement for the array_replace PHP function",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "config",
+ "configuration",
+ "options"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/options-resolver/tree/v7.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/options-resolver"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.33.0",
+ "version_normalized": "1.33.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "shasum": ""
+ },
+ "require": {
+ "ext-iconv": "*",
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "time": "2024-12-23T08:48:59+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/polyfill-mbstring"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.33.0",
+ "version_normalized": "1.33.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "time": "2025-01-02T08:10:11+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/polyfill-php80"
+ },
+ {
+ "name": "symfony/polyfill-php83",
+ "version": "v1.33.0",
+ "version_normalized": "1.33.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php83.git",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "time": "2025-07-08T02:45:35+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php83\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/polyfill-php83"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v7.4.4",
+ "version_normalized": "7.4.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "bfde13711f53f549e73b06d27b35a55207528877"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/bfde13711f53f549e73b06d27b35a55207528877",
+ "reference": "bfde13711f53f549e73b06d27b35a55207528877",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^2.5.3|^3.3"
+ },
+ "conflict": {
+ "nikic/php-parser": "<5.0",
+ "symfony/config": "<6.4",
+ "symfony/console": "<6.4",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<6.4",
+ "symfony/service-contracts": "<2.5",
+ "symfony/twig-bundle": "<6.4",
+ "symfony/yaml": "<6.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3|3.0"
+ },
+ "require-dev": {
+ "nikic/php-parser": "^5.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.4|^7.0|^8.0",
+ "symfony/console": "^6.4|^7.0|^8.0",
+ "symfony/dependency-injection": "^6.4|^7.0|^8.0",
+ "symfony/finder": "^6.4|^7.0|^8.0",
+ "symfony/http-client-contracts": "^2.5|^3.0",
+ "symfony/http-kernel": "^6.4|^7.0|^8.0",
+ "symfony/intl": "^6.4|^7.0|^8.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/routing": "^6.4|^7.0|^8.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^6.4|^7.0|^8.0"
+ },
+ "time": "2026-01-13T10:40:19+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to internationalize your application",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/translation/tree/v7.4.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/translation"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v3.6.1",
+ "version_normalized": "3.6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977",
+ "reference": "65a8bc82080447fae78373aa10f8d13b38338977",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "time": "2025-07-15T13:41:35+00:00",
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "3.6-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nicolas-grekas",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../symfony/translation-contracts"
+ },
+ {
+ "name": "team-reflex/discord-php",
+ "version": "v10.45.18",
+ "version_normalized": "10.45.18.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/discord-php/DiscordPHP.git",
+ "reference": "05a2b31b1301827e26a478c9e970da103a20bd67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/discord-php/DiscordPHP/zipball/05a2b31b1301827e26a478c9e970da103a20bd67",
+ "reference": "05a2b31b1301827e26a478c9e970da103a20bd67",
+ "shasum": ""
+ },
+ "require": {
+ "discord-php-helpers/collection": "^8.0",
+ "discord-php-helpers/voice": "^8.0 || dev-main",
+ "discord-php/http": "^10.1.7",
+ "discord/interactions": "^2.2",
+ "ext-zlib": "*",
+ "monolog/monolog": "^2.1.1 || ^3.0",
+ "nesbot/carbon": "^2.38 || ^3.0",
+ "php": "^8.1.2",
+ "psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
+ "ratchet/pawl": "^0.4.3",
+ "react/async": "^4.0 || ^3.0",
+ "react/cache": "^0.5 || ^0.6 || ^1.0",
+ "react/child-process": "^0.6.3",
+ "react/datagram": "^1.8",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0.0",
+ "symfony/options-resolver": "^5.1.11 || ^6.0 || ^7.0",
+ "trafficcophp/bytebuffer": "^0.3"
+ },
+ "require-dev": {
+ "carthage-software/mago": "1.1.0",
+ "davidcole1340/reactsh": "dev-master",
+ "friendsofphp/php-cs-fixer": "^3",
+ "laravel/pint": "^1.21",
+ "pestphp/pest": "^4.1.2",
+ "symfony/cache": "^5.4",
+ "symfony/var-dumper": "*",
+ "wyrihaximus/react-cache-redis": "^4.5"
+ },
+ "suggest": {
+ "ext-ev": "For a faster, and more performant loop.",
+ "ext-event": "For a faster, and more performant loop.",
+ "ext-fileinfo": "For function mime_content_type().",
+ "ext-gmp": "For 64 bit calculations on x86 (32 bit) PHP.",
+ "ext-mbstring": "For accurate calculations of string length when handling non-english characters.",
+ "ext-uv": "For a faster, and more performant loop. Preferred.",
+ "laracord/laracord": "Provides Laracord integration for DiscordPHP."
+ },
+ "time": "2026-02-11T16:00:11+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/Discord/functions.php"
+ ],
+ "psr-4": {
+ "Discord\\": "src/Discord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Valithor Obsidion",
+ "email": "valithor@discordphp.org"
+ }
+ ],
+ "description": "An unofficial API to interact with the voice and text service Discord.",
+ "support": {
+ "chat": "https://discord.gg/dphp",
+ "docs": "https://discord-php.github.io/DiscordPHP/",
+ "issues": "https://github.com/discord-php/DiscordPHP/issues",
+ "source": "https://github.com/discord-php/DiscordPHP/tree/v10.45.18",
+ "wiki": "https://github.com/discord-php/DiscordPHP/wiki"
+ },
+ "funding": [
+ {
+ "url": "https://valgorithms.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.paypal.me/valithor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/valzargaming",
+ "type": "github"
+ },
+ {
+ "url": "https://www.patreon.com/DiscordPHP",
+ "type": "patreon"
+ }
+ ],
+ "install-path": "../team-reflex/discord-php"
+ },
+ {
+ "name": "trafficcophp/bytebuffer",
+ "version": "v0.3",
+ "version_normalized": "0.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nesQuick/ByteBuffer.git",
+ "reference": "e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nesQuick/ByteBuffer/zipball/e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4",
+ "reference": "e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4",
+ "shasum": ""
+ },
+ "time": "2016-01-13T22:50:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "TrafficCophp": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ole 'nesQuick' Michaelis",
+ "email": "ole.michaelis@googlemail.com",
+ "homepage": "http://www.codestars.eu"
+ }
+ ],
+ "description": "Node.js inspired byte stream buffer for PHP.",
+ "keywords": [
+ "Buffer",
+ "Bytehandling",
+ "Socket",
+ "binary data",
+ "library",
+ "pack",
+ "stream",
+ "wrapper"
+ ],
+ "support": {
+ "issues": "https://github.com/nesQuick/ByteBuffer/issues",
+ "source": "https://github.com/nesQuick/ByteBuffer/tree/master"
+ },
+ "install-path": "../trafficcophp/bytebuffer"
+ }
+ ],
+ "dev": true,
+ "dev-package-names": []
+}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
new file mode 100644
index 0000000..3d616e9
--- /dev/null
+++ b/vendor/composer/installed.php
@@ -0,0 +1,395 @@
+ array(
+ 'name' => '__root__',
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '6d183d7a92b5a77ec97b393003963fca81a2f802',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev' => true,
+ ),
+ 'versions' => array(
+ '__root__' => array(
+ 'pretty_version' => 'dev-master',
+ 'version' => 'dev-master',
+ 'reference' => '6d183d7a92b5a77ec97b393003963fca81a2f802',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'carbonphp/carbon-doctrine-types' => array(
+ 'pretty_version' => '3.2.0',
+ 'version' => '3.2.0.0',
+ 'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'discord-php-helpers/collection' => array(
+ 'pretty_version' => 'v8.0.0',
+ 'version' => '8.0.0.0',
+ 'reference' => '27eae375a21d5086a665b803881cee09863b2269',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../discord-php-helpers/collection',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'discord-php-helpers/voice' => array(
+ 'pretty_version' => 'v8.0.5',
+ 'version' => '8.0.5.0',
+ 'reference' => '9c6ce3547f1463a2726d6726fd971ea26f99a1dc',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../discord-php-helpers/voice',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'discord-php/http' => array(
+ 'pretty_version' => 'v10.9.0',
+ 'version' => '10.9.0.0',
+ 'reference' => 'f2d281a435ec4f42b084dd1b843cfac91ac2fbbd',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../discord-php/http',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'discord/interactions' => array(
+ 'pretty_version' => '2.2.0',
+ 'version' => '2.2.0.0',
+ 'reference' => 'a6fc0c877b75cf5ff5811f2ea69c5cc4ad6ac457',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../discord/interactions',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'evenement/evenement' => array(
+ 'pretty_version' => 'v3.0.2',
+ 'version' => '3.0.2.0',
+ 'reference' => '0a16b0d71ab13284339abb99d9d2bd813640efbc',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../evenement/evenement',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'fig/http-message-util' => array(
+ 'pretty_version' => '1.1.5',
+ 'version' => '1.1.5.0',
+ 'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../fig/http-message-util',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'guzzlehttp/psr7' => array(
+ 'pretty_version' => '2.8.0',
+ 'version' => '2.8.0.0',
+ 'reference' => '21dc724a0583619cd1652f673303492272778051',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../guzzlehttp/psr7',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'monolog/monolog' => array(
+ 'pretty_version' => '3.10.0',
+ 'version' => '3.10.0.0',
+ 'reference' => 'b321dd6749f0bf7189444158a3ce785cc16d69b0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../monolog/monolog',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'nesbot/carbon' => array(
+ 'pretty_version' => '3.11.1',
+ 'version' => '3.11.1.0',
+ 'reference' => 'f438fcc98f92babee98381d399c65336f3a3827f',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../nesbot/carbon',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/clock' => array(
+ 'pretty_version' => '1.0.0',
+ 'version' => '1.0.0.0',
+ 'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/clock',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/clock-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '1.0',
+ ),
+ ),
+ 'psr/http-factory' => array(
+ 'pretty_version' => '1.1.0',
+ 'version' => '1.1.0.0',
+ 'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-factory',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/http-factory-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '1.0',
+ ),
+ ),
+ 'psr/http-message' => array(
+ 'pretty_version' => '1.1',
+ 'version' => '1.1.0.0',
+ 'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/http-message',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/http-message-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '1.0',
+ ),
+ ),
+ 'psr/log' => array(
+ 'pretty_version' => '3.0.2',
+ 'version' => '3.0.2.0',
+ 'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/log',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'psr/log-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '3.0.0',
+ ),
+ ),
+ 'psr/simple-cache' => array(
+ 'pretty_version' => '3.0.0',
+ 'version' => '3.0.0.0',
+ 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../psr/simple-cache',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ralouphie/getallheaders' => array(
+ 'pretty_version' => '3.0.3',
+ 'version' => '3.0.3.0',
+ 'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ralouphie/getallheaders',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ratchet/pawl' => array(
+ 'pretty_version' => 'v0.4.3',
+ 'version' => '0.4.3.0',
+ 'reference' => '2c582373c78271de32cb04c755c4c0db7e09c9c0',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ratchet/pawl',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'ratchet/rfc6455' => array(
+ 'pretty_version' => 'v0.4.0',
+ 'version' => '0.4.0.0',
+ 'reference' => '859d95f85dda0912c6d5b936d036d044e3af47ef',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ratchet/rfc6455',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/async' => array(
+ 'pretty_version' => 'v4.3.0',
+ 'version' => '4.3.0.0',
+ 'reference' => '635d50e30844a484495713e8cb8d9e079c0008a5',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/async',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/cache' => array(
+ 'pretty_version' => 'v1.2.0',
+ 'version' => '1.2.0.0',
+ 'reference' => 'd47c472b64aa5608225f47965a484b75c7817d5b',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/cache',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/child-process' => array(
+ 'pretty_version' => 'v0.6.7',
+ 'version' => '0.6.7.0',
+ 'reference' => '970f0e71945556422ee4570ccbabaedc3cf04ad3',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/child-process',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/datagram' => array(
+ 'pretty_version' => 'v1.10.0',
+ 'version' => '1.10.0.0',
+ 'reference' => '9236e1f5a67a6029be17d551e9858c487836c301',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/datagram',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/dns' => array(
+ 'pretty_version' => 'v1.14.0',
+ 'version' => '1.14.0.0',
+ 'reference' => '7562c05391f42701c1fccf189c8225fece1cd7c3',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/dns',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/event-loop' => array(
+ 'pretty_version' => 'v1.6.0',
+ 'version' => '1.6.0.0',
+ 'reference' => 'ba276bda6083df7e0050fd9b33f66ad7a4ac747a',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/event-loop',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/http' => array(
+ 'pretty_version' => 'v1.11.0',
+ 'version' => '1.11.0.0',
+ 'reference' => '8db02de41dcca82037367f67a2d4be365b1c4db9',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/http',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/promise' => array(
+ 'pretty_version' => 'v3.3.0',
+ 'version' => '3.3.0.0',
+ 'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/promise',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/socket' => array(
+ 'pretty_version' => 'v1.17.0',
+ 'version' => '1.17.0.0',
+ 'reference' => 'ef5b17b81f6f60504c539313f94f2d826c5faa08',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/socket',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'react/stream' => array(
+ 'pretty_version' => 'v1.4.0',
+ 'version' => '1.4.0.0',
+ 'reference' => '1e5b0acb8fe55143b5b426817155190eb6f5b18d',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../react/stream',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/clock' => array(
+ 'pretty_version' => 'v7.4.0',
+ 'version' => '7.4.0.0',
+ 'reference' => '9169f24776edde469914c1e7a1442a50f7a4e110',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/clock',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/deprecation-contracts' => array(
+ 'pretty_version' => 'v3.6.0',
+ 'version' => '3.6.0.0',
+ 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/options-resolver' => array(
+ 'pretty_version' => 'v7.4.0',
+ 'version' => '7.4.0.0',
+ 'reference' => 'b38026df55197f9e39a44f3215788edf83187b80',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/options-resolver',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/polyfill-mbstring' => array(
+ 'pretty_version' => 'v1.33.0',
+ 'version' => '1.33.0.0',
+ 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/polyfill-php80' => array(
+ 'pretty_version' => 'v1.33.0',
+ 'version' => '1.33.0.0',
+ 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/polyfill-php80',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/polyfill-php83' => array(
+ 'pretty_version' => 'v1.33.0',
+ 'version' => '1.33.0.0',
+ 'reference' => '17f6f9a6b1735c0f163024d959f700cfbc5155e5',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/polyfill-php83',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/translation' => array(
+ 'pretty_version' => 'v7.4.4',
+ 'version' => '7.4.4.0',
+ 'reference' => 'bfde13711f53f549e73b06d27b35a55207528877',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/translation',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/translation-contracts' => array(
+ 'pretty_version' => 'v3.6.1',
+ 'version' => '3.6.1.0',
+ 'reference' => '65a8bc82080447fae78373aa10f8d13b38338977',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../symfony/translation-contracts',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'symfony/translation-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '2.3|3.0',
+ ),
+ ),
+ 'team-reflex/discord-php' => array(
+ 'pretty_version' => 'v10.45.18',
+ 'version' => '10.45.18.0',
+ 'reference' => '05a2b31b1301827e26a478c9e970da103a20bd67',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../team-reflex/discord-php',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'trafficcophp/bytebuffer' => array(
+ 'pretty_version' => 'v0.3',
+ 'version' => '0.3.0.0',
+ 'reference' => 'e94e5c87c41bc79c0f738b0fa89bad11d27ae0b4',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../trafficcophp/bytebuffer',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php
new file mode 100644
index 0000000..d32d90c
--- /dev/null
+++ b/vendor/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 80200)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/vendor/discord-php-helpers/collection/.gitignore b/vendor/discord-php-helpers/collection/.gitignore
new file mode 100644
index 0000000..e198e89
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/.gitignore
@@ -0,0 +1,31 @@
+# general
+.env
+.DS_Store
+test*.php
+.idea
+.phplint-cache
+dump.html
+.vscode
+
+# phive
+/tools
+.phive
+
+# composer
+/vendor
+composer.lock
+
+# docs
+/build
+.phpdoc
+
+# php-cs-fixer
+.php_cs.cache
+.php_cs
+.php-cs-fixer.php
+.php-cs-fixer.cache
+
+# phpunit
+phpunit.log
+/.phpunit*
+/coverage
diff --git a/vendor/discord-php-helpers/collection/LICENSE b/vendor/discord-php-helpers/collection/LICENSE
new file mode 100644
index 0000000..531aaaa
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 DiscordPHP
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/discord-php-helpers/collection/README.md b/vendor/discord-php-helpers/collection/README.md
new file mode 100644
index 0000000..bfeb0c8
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/README.md
@@ -0,0 +1,15 @@
+# DiscordPHP Collection
+
+A collection library for DiscordPHP. Inspired by Laravel Collections.
+
+## Installation
+
+You can install the library via Composer:
+
+```bash
+composer require discord-php-helpers/collection
+```
+
+## License
+
+This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
diff --git a/vendor/discord-php-helpers/collection/composer.json b/vendor/discord-php-helpers/collection/composer.json
new file mode 100644
index 0000000..f3dde29
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "discord-php-helpers/collection",
+ "description": "A collection library for DiscordPHP. Inspired by Laravel Collections.",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Valithor Obsidion",
+ "email": "valzargaming@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "team-reflex/discord-php": "dev-master"
+ },
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord/"
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/collection/src/Discord/Helpers/Collection.php b/vendor/discord-php-helpers/collection/src/Discord/Helpers/Collection.php
new file mode 100644
index 0000000..3d3cd08
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/src/Discord/Helpers/Collection.php
@@ -0,0 +1,90 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Helpers;
+
+use JsonSerializable;
+
+/**
+ * Collection of items. Inspired by Laravel Collections.
+ *
+ * @since 5.0.0 No longer extends Laravel's BaseCollection
+ * @since 4.0.0
+ */
+class Collection implements ExCollectionInterface, JsonSerializable
+{
+ use CollectionTrait;
+ /**
+ * The collection discriminator.
+ *
+ * @var ?string
+ */
+ protected $discrim;
+
+ /**
+ * The items contained in the collection.
+ *
+ * @var array
+ */
+ protected $items;
+
+ /**
+ * Class type allowed into the collection.
+ *
+ * @var string
+ */
+ protected $class;
+
+ /**
+ * Create a new Collection.
+ *
+ * @param array $items
+ * @param ?string $discrim
+ * @param ?string $class
+ */
+ public function __construct(array $items = [], ?string $discrim = 'id', ?string $class = null)
+ {
+ $this->items = $items;
+ $this->discrim = $discrim;
+ $this->class = $class;
+ }
+
+ /**
+ * Creates a collection from an array.
+ *
+ * @param array $items
+ * @param ?string $discrim
+ * @param ?string $class
+ *
+ * @return ExCollectionInterface
+ */
+ public static function from(array $items = [], ?string $discrim = 'id', ?string $class = null)
+ {
+ return new Collection($items, $discrim, $class);
+ }
+
+ /**
+ * Creates a collection for a class.
+ *
+ * @param string $class
+ * @param ?string $discrim
+ *
+ * @return ExCollectionInterface
+ */
+ public static function for(string $class, ?string $discrim = 'id')
+ {
+ $items = [];
+
+ return new Collection($items, $discrim, $class);
+ }
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionInterface.php b/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionInterface.php
new file mode 100644
index 0000000..59a0afa
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionInterface.php
@@ -0,0 +1,54 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Helpers;
+
+use ArrayAccess;
+use Countable;
+use IteratorAggregate;
+use JsonSerializable;
+use Traversable;
+
+interface CollectionInterface extends ArrayAccess, JsonSerializable, IteratorAggregate, Countable
+{
+ public function get(string $discrim, $key);
+ public function set($offset, $value);
+ public function pull($key, $default = null);
+ public function fill($items): self;
+ public function push(...$items): self;
+ public function pushItem($item): self;
+ public function count(): int;
+ public function first();
+ public function last();
+ public function isset($offset): bool;
+ public function has(...$keys): bool;
+ public function filter(callable $callback);
+ public function find(callable $callback);
+ public function clear(): void;
+ public function map(callable $callback);
+ public function merge($collection): self;
+ /** @deprecated 10.42.0 Use `jsonSerialize` */
+ public function toArray(bool $assoc = true): array;
+ public function offsetExists($offset): bool;
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset);
+ public function offsetSet($offset, $value): void;
+ public function offsetUnset($offset): void;
+ public function serialize(int $flags = 0, ?int $depth = 512): string;
+ public function __serialize(): array;
+ public function unserialize(string $serialized): void;
+ public function __unserialize($data): void;
+ public function jsonSerialize(bool $assoc = true): array;
+ public function getIterator(): Traversable;
+ public function __debugInfo(): array;
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionTrait.php b/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionTrait.php
new file mode 100644
index 0000000..44a1944
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/src/Discord/Helpers/CollectionTrait.php
@@ -0,0 +1,721 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Helpers;
+
+/**
+ * Provides common functionality for collections.
+ *
+ * @property string|null $discrim The collection discriminator.
+ * @property array $items The items contained in the collection.
+ * @property string $class Class type allowed into the collection.
+ */
+trait CollectionTrait
+{
+
+ /**
+ * Gets an item from the collection.
+ *
+ * @param string $discrim
+ * @param mixed $key
+ *
+ * @return mixed
+ */
+ public function get(string $discrim, $key)
+ {
+ if ($discrim === $this->discrim && isset($this->items[$key])) {
+ return $this->items[$key];
+ }
+
+ foreach ($this->items as $item) {
+ if (is_array($item) && isset($item[$discrim]) && $item[$discrim] === $key) {
+ return $item;
+ } elseif (is_object($item) && $item->{$discrim} === $key) {
+ return $item;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets a value in the collection.
+ *
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function set($offset, $value)
+ {
+ // Don't insert elements that are not of type class.
+ if (null !== $this->class && ! ($value instanceof $this->class)) {
+ return;
+ }
+
+ $this->offsetSet($offset, $value);
+ }
+
+ /**
+ * Pulls an item from the collection.
+ *
+ * @param mixed $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function pull($key, $default = null)
+ {
+ if (isset($this->items[$key])) {
+ $default = $this->items[$key];
+ unset($this->items[$key]);
+ }
+
+ return $default;
+ }
+
+ /**
+ * Shifts an item from the collection.
+ *
+ * @return mixed
+ */
+ public function shift()
+ {
+ if (empty($this->items)) {
+ return null;
+ }
+
+ reset($this->items);
+ $key = key($this->items);
+ $value = array_shift($this->items);
+
+ return [$key => $value];
+ }
+
+ /**
+ * Fills an array of items into the collection.
+ *
+ * @param ExCollectionInterface|array $items
+ *
+ * @return self
+ */
+ public function fill($items): self
+ {
+ $items = $items instanceof CollectionInterface
+ ? $items->jsonSerialize()
+ : $items;
+ if (! is_array($items)) {
+ throw new \InvalidArgumentException('The fill method only accepts arrays or CollectionInterface instances.');
+ }
+
+ foreach ($items as $item) {
+ $this->pushItem($item);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Pushes items to the collection.
+ *
+ * @param mixed ...$items
+ *
+ * @return self
+ */
+ public function push(...$items): self
+ {
+ foreach ($items as $item) {
+ $this->pushItem($item);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Pushes a single item to the collection.
+ *
+ * @param mixed $item
+ *
+ * @return self
+ */
+ public function pushItem($item): self
+ {
+ if (null === $this->discrim) {
+ $this->items[] = $item;
+
+ return $this;
+ }
+
+ if (null !== $this->class && ! ($item instanceof $this->class)) {
+ return $this;
+ }
+
+ if (is_array($item)) {
+ $this->items[$item[$this->discrim]] = $item;
+ } elseif (is_object($item)) {
+ $this->items[$item->{$this->discrim}] = $item;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Counts the amount of objects in the collection.
+ *
+ * @return int
+ */
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Returns the first element of the collection.
+ *
+ * @return mixed
+ */
+ public function first()
+ {
+ foreach ($this->items as $item) {
+ return $item;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the last element of the collection.
+ *
+ * @return mixed
+ */
+ public function last()
+ {
+ $last = end($this->items);
+
+ if ($last !== false) {
+ reset($this->items);
+
+ return $last;
+ }
+
+ return null;
+ }
+
+ /**
+ * If the collection has an offset.
+ *
+ * @param mixed $offset
+ *
+ * @return bool
+ */
+ public function isset($offset): bool
+ {
+ return $this->offsetExists($offset);
+ }
+
+ /**
+ * Checks if the array has multiple offsets.
+ *
+ * @param string|int ...$keys
+ *
+ * @return bool
+ */
+ public function has(...$keys): bool
+ {
+ foreach ($keys as $key) {
+ if (! isset($this->items[$key])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Searches for a given value within the collection and returns the corresponding key if successful.
+ *
+ * @param mixed $needle
+ * @param bool $strict [optional]
+ *
+ * @return string|int|false
+ */
+ public function search(mixed $needle, bool $strict = false): string|int|false
+ {
+ return array_search($needle, $this->items, $strict);
+ }
+
+ /**
+ * Runs a filter callback over the collection and returns a new Collection
+ * based on the response of the callback.
+ *
+ * @param callable $callback
+ *
+ * @return ExCollectionInterface
+ *
+ * @todo This method will be typed to return a CollectionInterface in v11
+ */
+ public function filter(callable $callback)
+ {
+ $collection = new Collection([], $this->discrim, $this->class);
+
+ foreach ($this->items as $item) {
+ if ($callback($item)) {
+ $collection->push($item);
+ }
+ }
+
+ return $collection;
+ }
+
+ /**
+ * Runs a filter callback over the collection and returns the first item
+ * where the callback returns `true` when given the item.
+ *
+ * @param callable $callback
+ *
+ * @return mixed `null` if no items returns `true` when called in the `$callback`.
+ */
+ public function find(callable $callback)
+ {
+ foreach ($this->items as $item) {
+ if ($callback($item)) {
+ return $item;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the key of the first item that matches the callback.
+ *
+ * @param callable $callback
+ *
+ * @return mixed
+ */
+ public function find_key(callable $callback)
+ {
+ foreach ($this->items as $key => $item) {
+ if ($callback($item)) {
+ return $key;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks if any item matches the callback.
+ *
+ * @param callable $callback
+ *
+ * @return bool
+ */
+ public function any(callable $callback): bool
+ {
+ foreach ($this->items as $item) {
+ if ($callback($item)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if all items match the callback.
+ *
+ * @param callable $callback
+ *
+ * @return bool
+ */
+ public function all(callable $callback): bool
+ {
+ foreach ($this->items as $item) {
+ if (! $callback($item)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Splices the collection, removing a portion of the items and replacing them with the given replacement.
+ *
+ * @param int $offset
+ * @param ?int $length
+ * @param mixed $replacement
+ *
+ * @return self
+ */
+ public function splice(int $offset, ?int $length, mixed $replacement = []): self
+ {
+ array_splice($this->items, $offset, $length, $replacement);
+
+ return $this;
+ }
+
+ /**
+ * Clears the collection.
+ */
+ public function clear(): void
+ {
+ $this->items = [];
+ }
+
+ /**
+ * Slices the collection.
+ *
+ * @param int $offset
+ * @param ?int $length
+ * @param bool $preserve_keys
+ *
+ * @return ExCollectionInterface
+ */
+ public function slice(int $offset, ?int $length = null, bool $preserve_keys = false)
+ {
+ $items = $this->items;
+
+ $items = array_slice($items, $offset, $length, $preserve_keys);
+
+ return new Collection($items, $this->discrim, $this->class);
+ }
+
+ /**
+ * Sort through each item with a callback.
+ *
+ * @param callable|int|null $callback
+ *
+ * @return ExCollectionInterface
+ */
+ public function sort(callable|int|null $callback)
+ {
+ $items = $this->items;
+
+ $callback && is_callable($callback)
+ ? uasort($items, $callback)
+ : asort($items, $callback ?? SORT_REGULAR);
+
+ return new Collection($items, $this->discrim, $this->class);
+ }
+
+ /**
+ * Gets the difference between the items.
+ *
+ * If a callback is provided and is callable, it uses `array_udiff_assoc` to compute the difference.
+ * Otherwise, it uses `array_diff`.
+ *
+ * @param CollectionInterface|array $array
+ * @param ?callable $callback
+ *
+ * @return ExCollectionInterface
+ */
+ public function diff($items, ?callable $callback = null)
+ {
+ $items = $items instanceof CollectionInterface
+ ? $items->jsonSerialize()
+ : $items;
+
+ $diff = $callback && is_callable($callback)
+ ? array_udiff_assoc($this->items, $items, $callback)
+ : array_diff($this->items, $items);
+
+ return new Collection($diff, $this->discrim, $this->class);
+ }
+
+ /**
+ * Gets the intersection of the items.
+ *
+ * If a callback is provided and is callable, it uses `array_uintersect_assoc` to compute the intersection.
+ * Otherwise, it uses `array_intersect`.
+ *
+ * @param CollectionInterface|array $array
+ * @param ?callable $callback
+ *
+ * @return ExCollectionInterface
+ */
+ public function intersect($items, ?callable $callback = null)
+ {
+ $items = $items instanceof CollectionInterface
+ ? $items->jsonSerialize()
+ : $items;
+
+ $diff = $callback && is_callable($callback)
+ ? array_uintersect_assoc($this->items, $items, $callback)
+ : array_intersect($this->items, $items);
+
+ return new Collection($diff, $this->discrim, $this->class);
+ }
+
+ /**
+ * Applies the given callback function to each item in the collection.
+ *
+ * @param callable $callback
+ * @param mixed $arg
+ *
+ * @return ExCollectionInterface
+ */
+ public function walk(callable $callback, mixed $arg = null)
+ {
+ $items = $this->items;
+
+ array_walk($items, $callback, $arg);
+
+ return new Collection($items, $this->discrim, $this->class);
+ }
+
+ /**
+ * Reduces the collection to a single value using a callback function.
+ *
+ * @param callable $callback
+ * @param ?mixed $initial
+ *
+ * @return ExCollectionInterface
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ $items = $this->items;
+
+ $items = array_reduce($items, $callback, $initial);
+
+ return new Collection($items, $this->discrim, $this->class);
+ }
+
+ /**
+ * Runs a callback over the collection and creates a new Collection.
+ *
+ * @param callable $callback
+ *
+ * @return ExCollectionInterface
+ */
+ public function map(callable $callback)
+ {
+ $keys = array_keys($this->items);
+ $values = array_map($callback, array_values($this->items));
+
+ return new Collection(array_combine($keys, $values), $this->discrim, $this->class);
+ }
+
+ /**
+ * Returns unique items.
+ *
+ * @param int $flags
+ *
+ * @return ExCollectionInterface
+ */
+ public function unique(int $flags = SORT_STRING)
+ {
+ return new Collection(array_unique($this->items, $flags), $this->discrim, $this->class);
+ }
+
+ /**
+ * Merges another collection into this collection.
+ *
+ * @param $collection
+ *
+ * @return self
+ */
+ public function merge($collection): self
+ {
+ $items = $collection instanceof CollectionInterface
+ ? $collection->jsonSerialize()
+ : $collection;
+
+ $this->items = array_merge($this->items, $items);
+
+ return $this;
+ }
+
+ /**
+ * Converts the collection to an array.
+ *
+ * @param bool $assoc Whether to map keys to values.
+ *
+ * @deprecated 10.42.0 Use `jsonSerialize`
+ *
+ * @return array
+ */
+ public function toArray(bool $assoc = true): array
+ {
+ return $this->jsonSerialize($assoc);
+ }
+ /**
+ * Converts the items into a new collection.
+ *
+ * @return ExCollectionInterface
+ */
+ public function collect()
+ {
+ return new Collection($this->items, $this->discrim, $this->class);
+ }
+
+ /**
+ * Get the keys of the items.
+ *
+ * @since 10.2.0
+ *
+ * @return int[]|string[]
+ */
+ public function keys(): array
+ {
+ return array_keys($this->items);
+ }
+
+ /**
+ * Get the values of the items.
+ *
+ * @return array
+ */
+ public function values(): array
+ {
+ return array_values($this->items);
+ }
+
+ /**
+ * If the collection has an offset.
+ *
+ * @param mixed $offset
+ *
+ * @return bool
+ */
+ public function offsetExists($offset): bool
+ {
+ return isset($this->items[$offset]);
+ }
+
+ /**
+ * Gets an item from the collection.
+ *
+ * @param mixed $offset
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->items[$offset] ?? null;
+ }
+
+ /**
+ * Sets an item into the collection.
+ *
+ * @param mixed $offset
+ * @param mixed $value
+ */
+ public function offsetSet($offset, $value): void
+ {
+ // Attempt to use the value's discrim property as the key if offset is null
+ if (empty($offset)) {
+ if (is_array($value) && isset($value[$this->discrim])) {
+ $offset = $value[$this->discrim];
+ } elseif (is_object($value) && property_exists($value, $this->discrim)) {
+ $offset = $value->{$this->discrim};
+ }
+ }
+
+ $this->items[$offset] = $value;
+ }
+
+ /**
+ * Unsets an index from the collection.
+ *
+ * @param mixed $offset
+ */
+ public function offsetUnset($offset): void
+ {
+ unset($this->items[$offset]);
+ }
+
+ /**
+ * Returns the string representation of the collection.
+ *
+ * @param int $flags
+ * @param ?int $depth
+ *
+ * @return string
+ */
+ public function serialize(int $flags = 0, ?int $depth = 512): string
+ {
+ return json_encode($this->items, $flags, $depth);
+ }
+
+ /**
+ * Returns the string representation of the collection.
+ *
+ * @return array
+ */
+ public function __serialize(): array
+ {
+ return $this->items;
+ }
+
+ /**
+ * Unserializes the collection.
+ *
+ * @param string $serialized
+ */
+ public function unserialize(string $serialized): void
+ {
+ $this->items = json_decode($serialized);
+ }
+
+ /**
+ * Unserializes the collection.
+ *
+ * @param ExCollectionInterface|array $data
+ */
+ public function __unserialize($data): void
+ {
+ if ($data instanceof CollectionInterface) {
+ $data = $data->jsonSerialize();
+ }
+ if (! is_array($data)) {
+ throw new \InvalidArgumentException('The __unserialize method only accepts arrays or CollectionInterface instances.');
+ }
+
+ $this->items = $data;
+ }
+
+ /**
+ * Serializes the object to a value that can be serialized natively by json_encode().
+ *
+ * @param bool $assoc Whether to map keys to values.
+ *
+ * @return array
+ */
+ public function jsonSerialize(bool $assoc = true): array
+ {
+ return $assoc
+ ? $this->items
+ : array_values($this->items);
+ }
+
+ /**
+ * Returns an iterator for the collection.
+ *
+ * @return \Traversable
+ */
+ public function getIterator(): \Traversable
+ {
+ return new \ArrayIterator($this->items);
+ }
+
+ /**
+ * Returns an item that will be displayed for debugging.
+ *
+ * @return array
+ */
+ public function __debugInfo(): array
+ {
+ return $this->items;
+ }
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/collection/src/Discord/Helpers/ExCollectionInterface.php b/vendor/discord-php-helpers/collection/src/Discord/Helpers/ExCollectionInterface.php
new file mode 100644
index 0000000..01a3c07
--- /dev/null
+++ b/vendor/discord-php-helpers/collection/src/Discord/Helpers/ExCollectionInterface.php
@@ -0,0 +1,42 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Helpers;
+
+interface ExCollectionInterface extends CollectionInterface
+{
+ public function shift();
+ public function search(mixed $needle, bool $strict = false): string|int|false;
+ public function find_key(callable $callback);
+ public function any(callable $callback): bool;
+ public function all(callable $callback): bool;
+ /** @return ExCollectionInterface */
+ public function splice(int $offset, ?int $length, mixed $replacement = []): self;
+ public function clear(): void;
+ /** @return ExCollectionInterface */
+ public function slice(int $offset, ?int $length = null, bool $preserve_keys = false);
+ /** @return ExCollectionInterface */
+ public function sort(callable|int|null $callback);
+ /** @return ExCollectionInterface */
+ public function diff($items, ?callable $callback = null);
+ /** @return ExCollectionInterface */
+ public function intersect($items, ?callable $callback = null);
+ /** @return ExCollectionInterface */
+ public function walk(callable $callback, mixed $arg = null);
+ /** @return ExCollectionInterface */
+ public function reduce(callable $callback, $initial = null);
+ /** @return ExCollectionInterface */
+ public function unique(int $flags = SORT_STRING);
+ public function keys(): array;
+ public function values(): array;
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/voice/.editorconfig b/vendor/discord-php-helpers/voice/.editorconfig
new file mode 100644
index 0000000..00708e9
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.yml]
+indent_size = 2
+
+[*.js]
+indent_size = 2
diff --git a/vendor/discord-php-helpers/voice/.github/FUNDING.yml b/vendor/discord-php-helpers/voice/.github/FUNDING.yml
new file mode 100644
index 0000000..c4e3cba
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: [valzargaming, Log1x]
+patreon: DiscordPHP
diff --git a/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/bug_report.md b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..3f6c38f
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,39 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+
+---
+
+
+
+
+
+
+
+**Environment**
+- PHP Version:
+ - x.x.x
+- DiscordPHP Version:
+ - x.x.x
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+```php
+$discord->on(..., function () {
+ // ...
+});
+```
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/config.yml b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..aa1df25
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,4 @@
+contact_links:
+ - name: Question and Answers
+ url: https://github.com/discord-php/DiscordPHP/discussions/categories/q-a
+ about: If you have question about how to use this Library and have not find any solution in documentation or wiki, please do not make issues, instead go find or create one in Discussions section.
diff --git a/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/feature_request.md b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..8552ba0
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,22 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: features
+
+---
+
+
+
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/vendor/discord-php-helpers/voice/.github/dependabot.yml b/vendor/discord-php-helpers/voice/.github/dependabot.yml
new file mode 100644
index 0000000..a51bb0b
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "composer" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "daily"
diff --git a/vendor/discord-php-helpers/voice/.github/workflows/docs.yml b/vendor/discord-php-helpers/voice/.github/workflows/docs.yml
new file mode 100644
index 0000000..04f2353
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/workflows/docs.yml
@@ -0,0 +1,36 @@
+name: Build Docs
+on:
+ push:
+ release:
+ types: [published]
+
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+ if: github.event_name == 'release' || contains(github.event.head_commit.message, 'build docs')
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: '8.1'
+ extensions: iconv
+ tools: phive
+ - name: Install phpDocumentor
+ run: phive install phpDocumentor --trust-gpg-keys 67F861C3D889C656,6DA3ACC4991FFAE5
+ - name: Build class reference
+ run: ./tools/phpDocumentor
+ - name: Build documentation
+ run: |
+ cd ./docs
+ yarn install
+ yarn build
+ sudo mv public/* ../build
+ - name: Publish docs
+ uses: JamesIves/github-pages-deploy-action@3.7.1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BRANCH: gh-pages
+ FOLDER: build
+ CLEAN: true
diff --git a/vendor/discord-php-helpers/voice/.github/workflows/unit.yml b/vendor/discord-php-helpers/voice/.github/workflows/unit.yml
new file mode 100644
index 0000000..054c3f6
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.github/workflows/unit.yml
@@ -0,0 +1,42 @@
+name: Unit Tests
+on:
+ workflow_dispatch:
+
+env:
+ DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
+ TEST_CHANNEL: ${{ secrets.TEST_CHANNEL }}
+ TEST_CHANNEL_NAME: ${{ secrets.TEST_CHANNEL_NAME }}
+
+jobs:
+ unit-lint:
+ name: PHPUnit and Lint
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'no test')"
+ concurrency: phpunit
+ strategy:
+ matrix:
+ php:
+ - '8.4'
+ - '8.3'
+ - '8.2'
+ - '8.1'
+ - '8.0'
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: uv, zlib, mbstring
+ tools: phpunit, phplint
+ - name: Install dependencies
+ run: composer install
+ - name: Redis Server in GitHub Actions
+ uses: supercharge/redis-github-action@1.4.0
+ # - name: Memcached Service
+ # uses: niden/actions-memcached@v7
+ - name: Run PHPUnit
+ run: phpunit
+ - name: Run PHPLint
+ run: phplint
diff --git a/vendor/discord-php-helpers/voice/.gitignore b/vendor/discord-php-helpers/voice/.gitignore
new file mode 100644
index 0000000..213be65
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.gitignore
@@ -0,0 +1,32 @@
+# general
+.env
+.DS_Store
+test*.php
+.idea
+.phplint-cache
+dump.html
+.vscode
+
+# phive
+/tools
+.phive
+
+# composer
+/vendor
+composer.lock
+
+# docs
+/build
+.phpdoc
+
+# php-cs-fixer
+.php_cs.cache
+.php_cs
+.php-cs-fixer.php
+.php-cs-fixer.cache
+
+# phpunit
+phpunit.log
+/.phpunit*
+/coverage
+/.vs
diff --git a/vendor/discord-php-helpers/voice/.php-cs-fixer.dist.php b/vendor/discord-php-helpers/voice/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..bff656c
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.php-cs-fixer.dist.php
@@ -0,0 +1,102 @@
+
+
+This file is subject to the MIT license that is bundled
+with this source code in the LICENSE.md file.
+EOF;
+
+$fixers = [
+ 'blank_line_after_namespace',
+ 'braces',
+ 'class_definition',
+ 'elseif',
+ 'encoding',
+ 'full_opening_tag',
+ 'function_declaration',
+ 'lowercase_keywords',
+ 'method_argument_space',
+ 'no_closing_tag',
+ 'no_spaces_after_function_name',
+ 'no_spaces_inside_parenthesis',
+ 'no_trailing_whitespace',
+ 'no_trailing_whitespace_in_comment',
+ 'single_blank_line_at_eof',
+ 'single_class_element_per_statement',
+ 'single_import_per_statement',
+ 'single_line_after_imports',
+ 'switch_case_semicolon_to_colon',
+ 'switch_case_space',
+ 'visibility_required',
+ 'blank_line_after_opening_tag',
+ 'no_multiline_whitespace_around_double_arrow',
+ 'no_empty_statement',
+ 'include',
+ 'no_trailing_comma_in_list_call',
+ 'not_operator_with_successor_space',
+ 'no_leading_namespace_whitespace',
+ 'no_blank_lines_after_class_opening',
+ 'no_blank_lines_after_phpdoc',
+ 'object_operator_without_whitespace',
+ 'binary_operator_spaces',
+ 'phpdoc_indent',
+ 'general_phpdoc_tag_rename',
+ 'phpdoc_inline_tag_normalizer',
+ 'phpdoc_tag_type',
+ 'phpdoc_no_access',
+ 'phpdoc_no_package',
+ 'phpdoc_scalar',
+ 'phpdoc_summary',
+ 'phpdoc_trim',
+ 'phpdoc_var_without_name',
+ 'no_leading_import_slash',
+ 'no_trailing_comma_in_singleline_array',
+ 'single_blank_line_before_namespace',
+ 'single_quote',
+ 'no_singleline_whitespace_before_semicolons',
+ 'cast_spaces',
+ 'standardize_not_equals',
+ 'ternary_operator_spaces',
+ 'trim_array_spaces',
+ 'unary_operator_spaces',
+ 'no_unused_imports',
+ 'no_useless_else',
+ 'no_useless_return',
+ 'phpdoc_no_empty_return',
+ 'no_extra_blank_lines',
+ 'multiline_whitespace_before_semicolons',
+];
+
+$rules = [
+ 'concat_space' => ['spacing' => 'none'],
+ 'phpdoc_no_alias_tag' => ['replacements' => ['type' => 'var']],
+ 'array_syntax' => ['syntax' => 'short'],
+ 'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => true],
+ 'header_comment' => ['header' => $header],
+ 'indentation_type' => true,
+ 'phpdoc_align' => [
+ 'align' => 'vertical',
+ 'tags' => ['param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', 'method'],
+ ],
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'constant_case' => ['case' => 'lower'],
+ 'echo_tag_syntax' => ['format' => 'long'],
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
+];
+
+foreach ($fixers as $fix) {
+ $rules[$fix] = true;
+}
+
+$config = new PhpCsFixer\Config();
+
+return $config
+ ->setRules($rules)
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->exclude('examples')
+ ->in(__DIR__)
+ );
diff --git a/vendor/discord-php-helpers/voice/.phplint.yml b/vendor/discord-php-helpers/voice/.phplint.yml
new file mode 100644
index 0000000..8f3512b
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/.phplint.yml
@@ -0,0 +1,7 @@
+path: ./
+jobs: 10
+extensions:
+ - php
+exclude:
+ - vendor
+warning: false
diff --git a/vendor/discord-php-helpers/voice/CONTRIBUTING.md b/vendor/discord-php-helpers/voice/CONTRIBUTING.md
new file mode 100644
index 0000000..7d0a50d
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+We are open to contributions. However, please make sure you follow our coding standards (PSR-4 autoloading and custom styling). Please run php-cs-fixer before opening a pull request by running ``composer run-script cs.``
+
+Please only use the issue tracker for submitting issues with the code. If you have questions about how to use DiscordPHP, hop over to our discord at [](https://discord.gg/dphp)
diff --git a/vendor/discord-php-helpers/voice/LICENSE b/vendor/discord-php-helpers/voice/LICENSE
new file mode 100644
index 0000000..531aaaa
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2025 DiscordPHP
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/discord-php-helpers/voice/LICENSE.md b/vendor/discord-php-helpers/voice/LICENSE.md
new file mode 100644
index 0000000..52ab769
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2022 David Cole and all contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/discord-php-helpers/voice/README.md b/vendor/discord-php-helpers/voice/README.md
new file mode 100644
index 0000000..1c9c29e
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/README.md
@@ -0,0 +1,106 @@
+DiscordPHP Voice
+====
+[](https://packagist.org/packages/team-reflex/discord-php) [](https://packagist.org/packages/team-reflex/discord-php) [](https://packagist.org/packages/team-reflex/discord-php) [](https://packagist.org/packages/team-reflex/discord-php)
+
+[](https://discord.gg/dphp)
+
+A wrapper for the official [Discord](https://discordapp.com) REST, gateway and voice APIs. Documentation is [available here](http://discord-php.github.io/DiscordPHP), albeit limited at the moment, as well as a class reference. Feel free to ask questions in the Discord server above.
+
+For testing and stability it would be greatly appreciated if you were able to add our test bot to your server. We don't store any data - the bot simply idles and does not interact with anyone and is used to test stability with large numbers of guilds. You can invite the bot [here.](https://discord.com/oauth2/authorize?client_id=157746770539970560&scope=bot)
+
+## Cache Interface (experimental)
+> **Warning**
+> This branch contains an experimental feature, do not use it in production! See [the wiki page for more information](https://github.com/discord-php/DiscordPHP/wiki/Cache-Interface) on how to set it up.
+
+## FAQ
+
+1. Can I run DiscordPHP on a webserver (e.g. Apache, nginx)?
+ - No, DiscordPHP will only run in CLI. If you want to have an interface for your bot you can integrate [react/http](https://github.com/ReactPHP/http) with your bot and run it through CLI.
+2. PHP is running out of memory?
+ - Try unlimit your PHP memory using `ini_set('memory_limit', '-1');`.
+
+## Getting Started
+
+Before you start using this Library, you **need** to know how PHP works, you need to know how Event Loops and Promises work. This is a fundamental requirement before you start. Without this knowledge, you will only suffer.
+
+### Requirements
+
+- [PHP 8.0](https://php.net) or higher (latest version recommended)
+ - x86 (32-bit) PHP requires [`ext-gmp`](https://www.php.net/manual/en/book.gmp.php) enabled.
+- [`ext-json`](https://www.php.net/manual/en/book.json.php)
+- [`ext-zlib`](https://www.php.net/manual/en/book.zlib.php)
+
+#### Recommended Extensions
+
+- One of [`ext-uv`](https://github.com/amphp/ext-uv) (recommended), `ext-ev` or `ext-event` for a faster, and more performant event loop.
+- [`ext-mbstring`](https://www.php.net/manual/en/book.mbstring.php) if handling non-latin characters.
+
+#### Voice Requirements
+
+- 64-bit PHP
+- [`ext-sodium`](https://www.php.net/manual/en/book.sodium.php)
+- [FFmpeg](https://ffmpeg.org/)
+
+### Windows and SSL
+
+Unfortunately PHP on Windows does not have access to the Windows Certificate Store. This is an issue because TLS gets used and as such certificate verification gets applied (turning this off is **not** an option).
+
+You will notice this issue by your script exiting immediately after one loop turn without any errors.
+
+As such users of this library need to download a [Certificate Authority extract](https://curl.haxx.se/docs/caextract.html) from the cURL website.
+The path to the caextract must be set in the [`php.ini`](https://secure.php.net/manual/en/openssl.configuration.php) for `openssl.cafile`.
+
+### Installing DiscordPHP
+
+DiscordPHP is installed using [Composer](https://getcomposer.org).
+
+1. Run `composer require team-reflex/discord-php`. This will install the latest stable release.
+ - If you would like, you can also install the development branch by running `composer require team-reflex/discord-php dev-master`.
+2. Include the Composer autoload file at the top of your main file:
+ - `include __DIR__.'/vendor/autoload.php';`
+3. Make a bot!
+
+### Basic Example
+
+```php
+ 'bot-token',
+ 'intents' => Intents::getDefaultIntents()
+// | Intents::MESSAGE_CONTENT, // Note: MESSAGE_CONTENT is privileged, see https://dis.gd/mcfaq
+]);
+
+$discord->on('ready', function (Discord $discord) {
+ echo "Bot is ready!", PHP_EOL;
+
+ // Listen for messages.
+ $discord->on(Event::MESSAGE_CREATE, function (Message $message, Discord $discord) {
+ echo "{$message->author->username}: {$message->content}", PHP_EOL;
+ // Note: MESSAGE_CONTENT intent must be enabled to get the content if the bot is not mentioned/DMed.
+ });
+});
+
+$discord->run();
+```
+
+See [examples folder](examples) for more.
+
+## Documentation
+
+Documentation for the latest version can be found [here](//discord-php.github.io/DiscordPHP/guide). Community contributed tutorials can be found on the [wiki](//github.com/discord-php/DiscordPHP/wiki).
+
+## Contributing
+
+We are open to contributions. However, please make sure you follow our coding standards (PSR-4 autoloading and custom styling). Please run php-cs-fixer before opening a pull request by running `composer run-script cs`.
+
+## License
+
+MIT License, © David Cole and other contributers 2016-present.
diff --git a/vendor/discord-php-helpers/voice/composer.json b/vendor/discord-php-helpers/voice/composer.json
new file mode 100644
index 0000000..4601b92
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "discord-php-helpers/voice",
+ "description": "A voice library for DiscordPHP",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ },
+ {
+ "name": "Alexandre Candeias",
+ "email": "alexandreluisbarreto@gmail.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/discord-php/DiscordPHP-Voice/issues",
+ "wiki": "https://github.com/discord-php/DiscordPHP/wiki",
+ "docs": "https://discord-php.github.io/DiscordPHP/",
+ "chat": "https://discord.gg/dphp"
+ },
+ "require": {
+ "php": "^8.1.2"
+ },
+ "require-dev": {
+ "team-reflex/discord-php": "dev-master",
+ "ext-ffi": "*",
+ "friendsofphp/php-cs-fixer": "^3"
+ },
+ "autoload": {
+ "psr-4": {
+ "Discord\\": "src/Discord"
+ }
+ },
+ "scripts": {
+ "pint": ["./vendor/bin/pint --config ./pint.json"],
+ "cs": ["./vendor/bin/php-cs-fixer fix"],
+ "cs-unsupported": ["./vendor/bin/php-cs-fixer fix --allow-unsupported-php-version yes"],
+ "unit": ["./vendor/bin/phpunit --testdox"],
+ "coverage": ["XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html coverage --testdox"]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/vendor/discord-php-helpers/voice/phpdoc.dist.xml b/vendor/discord-php-helpers/voice/phpdoc.dist.xml
new file mode 100644
index 0000000..fe35c30
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/phpdoc.dist.xml
@@ -0,0 +1,27 @@
+
+
+ DiscordPHP Documentation
+
+ build
+
+
+
+
+ src
+
+ reference
+
+
+
+ guide
+
+ guide
+
+
+
+
diff --git a/vendor/discord-php-helpers/voice/phpunit.xml b/vendor/discord-php-helpers/voice/phpunit.xml
new file mode 100644
index 0000000..32e2b18
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/phpunit.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ tests
+
+
+
+
+
+
+ src
+
+
+
diff --git a/vendor/discord-php-helpers/voice/pint.json b/vendor/discord-php-helpers/voice/pint.json
new file mode 100644
index 0000000..49c5a7f
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/pint.json
@@ -0,0 +1,6 @@
+{
+ "preset": "psr12",
+ "rules": {
+ "declare_strict_types": true
+ }
+}
\ No newline at end of file
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Factory/SocketFactory.php b/vendor/discord-php-helpers/voice/src/Discord/Factory/SocketFactory.php
new file mode 100644
index 0000000..e530288
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Factory/SocketFactory.php
@@ -0,0 +1,51 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Factory;
+
+use Discord\Voice\Client\UDP;
+use Discord\Voice\Client\WS;
+use React\Datagram\Factory;
+use React\Dns\Resolver\Factory as DnsFactory;
+
+final class SocketFactory extends Factory
+{
+ protected ?WS $ws;
+
+ public function __construct($loop = null, $resolver = null, ?WS $ws = null)
+ {
+ if (null === $resolver) {
+ $resolver = (new DnsFactory())->createCached($ws->data['dnsConfig'], $loop);
+ }
+
+ parent::__construct($loop, $resolver);
+
+ if ($ws !== null) {
+ $this->ws = $ws;
+ }
+ }
+
+ public function createClient($address)
+ {
+ $loop = $this->loop;
+
+ return $this->resolveAddress($address)->then(function ($address) use ($loop) {
+ $socket = @\stream_socket_client($address, $errno, $errstr);
+ if (! $socket) {
+ throw new \Exception('Unable to create client socket: '.$errstr, $errno);
+ }
+
+ return new UDP($loop, $socket, ws: $this?->ws);
+ });
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Parts/Voice/UserConnected.php b/vendor/discord-php-helpers/voice/src/Discord/Parts/Voice/UserConnected.php
new file mode 100644
index 0000000..54a1aef
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Parts/Voice/UserConnected.php
@@ -0,0 +1,26 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Parts\Voice;
+
+use Discord\Parts\Part;
+
+class UserConnected extends Part
+{
+ /**
+ * {@inheritDoc}
+ */
+ protected $fillable = [
+ 'user_id',
+ ];
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/AbstractBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/AbstractBuffer.php
new file mode 100644
index 0000000..52d16d9
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/AbstractBuffer.php
@@ -0,0 +1,28 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+abstract class AbstractBuffer implements ReadableBuffer, WriteableBuffer
+{
+ abstract public function __construct($argument);
+
+ abstract public function __toString(): string;
+
+ abstract public function length(): int;
+
+ abstract public function getLastEmptyPosition(): int;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/Buffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/Buffer.php
new file mode 100644
index 0000000..4839f54
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/Buffer.php
@@ -0,0 +1,307 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * Helper class for handling binary data.
+ *
+ * @author alexandre433
+ *
+ * @throws \InvalidArgumentException If invalid arguments are provided or buffer overflows.
+ */
+class Buffer extends AbstractBuffer implements \ArrayAccess
+{
+ use BufferArrayAccessTrait;
+
+ protected \SplFixedArray $buffer;
+
+ public function __construct($argument)
+ {
+ is_string($argument)
+ ? $this->initializeStructs(strlen($argument), $argument)
+ : (is_int($argument)
+ ? $this->initializeStructs($argument, pack(FormatPackEnum::x->value."$argument"))
+ : throw new \InvalidArgumentException('Constructor argument must be an binary string or integer'));
+ }
+
+ public function __toString(): string
+ {
+ return implode('', iterator_to_array($this->buffer, false));
+ }
+
+ public static function make($argument): static
+ {
+ return new static($argument);
+ }
+
+ protected function initializeStructs($length, string $content): void
+ {
+ $this->buffer = new \SplFixedArray($length);
+ for ($i = 0; $i < $length; $i++) {
+ $this->buffer[$i] = $content[$i];
+ }
+ }
+
+ /**
+ * Inserts a value into the buffer at the specified offset.
+ *
+ * @param FormatPackEnum|string $format
+ * @param mixed $value
+ * @param int $offset
+ * @param ?int $length
+ * @return Buffer
+ */
+ protected function insert($format, $value, int $offset, ?int $length = null): self
+ {
+ $bytes = pack($format?->value ?? $format, $value);
+
+ if (null === $length) {
+ $length = strlen($bytes);
+ }
+
+ for ($i = 0; $i < strlen($bytes); $i++) {
+ $this->buffer[$offset++] = $bytes[$i];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Extracts a value from the buffer at the specified offset.
+ *
+ * @param FormatPackEnum|string $format
+ * @param int $offset
+ * @param int $length
+ * @return mixed
+ */
+ protected function extract(FormatPackEnum|string $format, int $offset, int $length)
+ {
+ $encoded = '';
+ for ($i = 0; $i < $length; $i++) {
+ $encoded .= $this->buffer->offsetGet($offset + $i);
+ }
+
+ if ($format == FormatPackEnum::N && PHP_INT_SIZE <= 4) {
+ [, $h, $l] = unpack('n*', $encoded);
+ $result = $l + $h * 0x010000;
+ } elseif ($format == FormatPackEnum::V && PHP_INT_SIZE <= 4) {
+ [, $h, $l] = unpack('v*', $encoded);
+ $result = $h + $l * 0x010000;
+ } else {
+ [, $result] = unpack($format?->value ?? $format, $encoded);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Checks if the actual value exceeds the expected maximum size.
+ *
+ * @param mixed $excpectedMax
+ * @param mixed $actual
+ * @throws \InvalidArgumentException
+ * @return static
+ */
+ protected function checkForOverSize($expectedMax, string|int $actual): self
+ {
+ if ($actual > $expectedMax) {
+ throw new \InvalidArgumentException('actual exceeded expectedMax limit');
+ }
+
+ return $this;
+ }
+
+ public function length(): int
+ {
+ return $this->buffer->getSize();
+ }
+
+ public function getLastEmptyPosition(): int
+ {
+ foreach ($this->buffer as $key => $value) {
+ if (empty(trim($value))) {
+ return $key;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Writes a string to the buffer at the specified offset.
+ *
+ * @param string $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function write($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $length = strlen($value);
+ $this->insert('a'.$length, $value, $offset, $length);
+
+ return $this;
+ }
+
+ /**
+ * Writes an 8-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt8($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::C;
+ $this->checkForOverSize(0xff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 16-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt16BE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::n;
+ $this->checkForOverSize(0xffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 16-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt16LE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::v;
+ $this->checkForOverSize(0xffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 32-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt32BE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::N;
+ $this->checkForOverSize(0xffffffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 32-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ #[\Override]
+ public function writeInt32LE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::V;
+ $this->checkForOverSize(0xffffffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Reads a string from the buffer at the specified offset.
+ *
+ * @param int $offset The offset to read from.
+ * @param int $length The length of the string to read.
+ * @return string The data read.
+ */
+ public function read(int $offset, int $length)
+ {
+ return $this->extract('a'.$length, $offset, $length);
+ }
+
+ public function readInt8(int $offset)
+ {
+ $format = FormatPackEnum::C;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt16BE(int $offset)
+ {
+ $format = FormatPackEnum::n;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt16LE(int $offset)
+ {
+ $format = FormatPackEnum::v;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt32BE(int $offset)
+ {
+ $format = FormatPackEnum::N;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt32LE(int $offset)
+ {
+ $format = FormatPackEnum::V;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/BufferArrayAccessTrait.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/BufferArrayAccessTrait.php
new file mode 100644
index 0000000..e052dde
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/BufferArrayAccessTrait.php
@@ -0,0 +1,216 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * @author Valithor Obsidion
+ */
+trait BufferArrayAccessTrait
+{
+ /**
+ * Writes a 32-bit unsigned integer with big endian.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt32BE(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::N, $value, $offset, 3);
+ }
+
+ /**
+ * Writes a 64-bit unsigned integer with little endian.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt64LE(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::P, $value, $offset, 8);
+ }
+
+ /**
+ * Writes a signed integer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeInt(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::N, $value, $offset, 4);
+ }
+
+ /**
+ * Writes a unsigned integer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::I, $value, $offset, 4);
+ }
+
+ /**
+ * Reads a signed integer.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readInt(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::N, $offset, 4);
+ }
+
+ /**
+ * Reads a signed integer.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readUInt(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::I, $offset, 4);
+ }
+
+ /**
+ * Writes an unsigned big endian short.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeShort(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::n, $value, $offset, 2);
+ }
+
+ /**
+ * Reads an unsigned big endian short.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readShort(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::n, $offset, 4);
+ }
+
+ /**
+ * Reads a unsigned integer with little endian.
+ *
+ * @param int $offset The offset that will be read.
+ *
+ * @return int The value that is at the specified offset.
+ */
+ public function readUIntLE(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::I, $offset, 3);
+ }
+
+ public function readChar(int $offset): string
+ {
+ return $this->extract(FormatPackEnum::c, $offset, 1);
+ }
+
+ public function readUChar(int $offset): string
+ {
+ return $this->extract(FormatPackEnum::C, $offset, 1);
+ }
+
+ /**
+ * Writes a char.
+ *
+ * @param string $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeChar(string $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::c, $value, $offset, FormatPackEnum::c->getLength());
+ }
+
+ /**
+ * Writes raw binary to the buffer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written at.
+ */
+ public function writeRaw(int $value, int $offset): void
+ {
+ $this->buffer[$offset] = $value;
+ }
+
+ /**
+ * Writes a binary string to the buffer.
+ *
+ * @param string $value The value that will be written.
+ * @param int $offset The offset that the value will be written at.
+ */
+ public function writeRawString(string $value, int $offset): void
+ {
+ for ($i = 0; $i < strlen($value); ++$i) {
+ $this->buffer[$offset++] = $value[$i];
+ }
+ }
+
+ /**
+ * Gets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->buffer[$key] ?? null;
+ }
+
+ /**
+ * Checks if an attribute exists via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ *
+ * @return bool Whether the offset exists.
+ */
+ public function offsetExists($key): bool
+ {
+ return isset($this->buffer[$key]);
+ }
+
+ /**
+ * Sets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ * @param mixed $value The attribute value.
+ */
+ public function offsetSet($key, $value): void
+ {
+ $this->buffer[$key] = $value;
+ }
+
+ /**
+ * Unsets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param string $key The attribute key.
+ */
+ public function offsetUnset($key): void
+ {
+ if (isset($this->buffer[$key])) {
+ unset($this->buffer[$key]);
+ }
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/FormatPackEnum.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/FormatPackEnum.php
new file mode 100644
index 0000000..eab5ea0
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/FormatPackEnum.php
@@ -0,0 +1,178 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * @link https://www.php.net/manual/en/function.pack.php
+ */
+enum FormatPackEnum: string
+{
+ /**
+ * NUL-padded string.
+ */
+ case a = 'a';
+
+ /**
+ * SPACE-padded string.
+ */
+ case A = 'A';
+
+ /**
+ * Hex string, low nibble first.
+ */
+ case h = 'h';
+
+ /**
+ * Hex string, high nibble first.
+ */
+ case H = 'H';
+
+ /**
+ * signed char.
+ */
+ case c = 'c';
+
+ /**
+ * unsigned char.
+ */
+ case C = 'C';
+
+ /**
+ * signed short (always 16 bit, machine byte order).
+ */
+ case s = 's';
+
+ /**
+ * unsigned short (always 16 bit, machine byte order).
+ */
+ case S = 'S';
+
+ /**
+ * unsigned short (always 16 bit, big endian byte order).
+ */
+ case n = 'n';
+
+ /**
+ * unsigned short (always 16 bit, little endian byte order).
+ */
+ case v = 'v';
+
+ /**
+ * signed integer (machine dependent size and byte order).
+ */
+ case i = 'i';
+
+ /**
+ * unsigned integer (machine dependent size and byte order).
+ */
+ case I = 'I';
+
+ /**
+ * signed long (always 32 bit, machine byte order).
+ */
+ case l = 'l';
+
+ /**
+ * unsigned long (always 32 bit, machine byte order).
+ */
+ case L = 'L';
+
+ /**
+ * unsigned long (always 32 bit, big endian byte order).
+ */
+ case N = 'N';
+
+ /**
+ * unsigned long (always 32 bit, little endian byte order).
+ */
+ case V = 'V';
+
+ /**
+ * signed long long (always 64 bit, machine byte order).
+ */
+ case q = 'q';
+
+ /**
+ * unsigned long long (always 64 bit, machine byte order).
+ */
+ case Q = 'Q';
+
+ /**
+ * unsigned long long (always 64 bit, big endian byte order).
+ */
+ case J = 'J';
+
+ /**
+ * unsigned long long (always 64 bit, little endian byte order).
+ */
+ case P = 'P';
+
+ /**
+ * float (machine dependent size and representation).
+ */
+ case f = 'f';
+
+ /**
+ * float (machine dependent size, little endian byte order).
+ */
+ case g = 'g';
+
+ /**
+ * float (machine dependent size, big endian byte order).
+ */
+ case G = 'G';
+
+ /**
+ * double (machine dependent size and representation).
+ */
+ case d = 'd';
+
+ /**
+ * double (machine dependent size, little endian byte order).
+ */
+ case e = 'e';
+
+ /**
+ * double (machine dependent size, big endian byte order).
+ */
+ case E = 'E';
+
+ /**
+ * NUL byte.
+ */
+ case x = 'x';
+
+ /**
+ * Back up one byte.
+ */
+ case X = 'X';
+
+ /**
+ * NUL-padded string.
+ */
+ case Z = 'Z';
+
+ /**
+ * NUL-fill to absolute position.
+ */
+ case At = '@';
+
+ public function getLength(): int
+ {
+ return match ($this) {
+ self::n, self::v => 2,
+ self::N, self::V => 4,
+ self::c, self::C => 1,
+ default => throw new \InvalidArgumentException('Invalid format pack'),
+ };
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/ReadableBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/ReadableBuffer.php
new file mode 100644
index 0000000..bc43888
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/ReadableBuffer.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+interface ReadableBuffer
+{
+ public function read(int $offset, int $length);
+
+ public function readInt8(int $offset);
+
+ public function readInt16BE(int $offset);
+
+ public function readInt16LE(int $offset);
+
+ public function readInt32BE(int $offset);
+
+ public function readInt32LE(int $offset);
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/WriteableBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/WriteableBuffer.php
new file mode 100644
index 0000000..343e56d
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ByteBuffer/WriteableBuffer.php
@@ -0,0 +1,67 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+interface WriteableBuffer
+{
+ public function write($value, ?int $offset = null): self;
+
+ /**
+ * Write an int8 to the buffer.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt8($value, ?int $offset = null): self;
+
+ /**
+ * Write an int16 to the buffer in big-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt16BE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int16 to the buffer in little-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt16LE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int32 to the buffer in big-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt32BE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int32 to the buffer in little-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt32LE($value, ?int $offset = null): self;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client.php
new file mode 100644
index 0000000..75504cd
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+/**
+ * The Discord voice client.
+ *
+ * @since 10.19.0
+ */
+class Client extends VoiceClient
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/HeaderValuesEnum.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/HeaderValuesEnum.php
new file mode 100644
index 0000000..ca9c708
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/HeaderValuesEnum.php
@@ -0,0 +1,40 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Client;
+
+/**
+ * Enum for header values used in Discord voice client.
+ *
+ * @since 10.19.0
+ */
+enum HeaderValuesEnum: int
+{
+ case RTP_HEADER_OR_NONCE_LENGTH = 12;
+
+ case RTP_VERSION_PAD_EXTEND_INDEX = 0;
+
+ case RTP_VERSION_PAD_EXTEND = 0x80;
+
+ case RTP_PAYLOAD_INDEX = 1;
+
+ case RTP_PAYLOAD_TYPE = 0x78;
+
+ case SEQ_INDEX = 2;
+
+ case TIMESTAMP_OR_NONCE_INDEX = 4;
+
+ case SSRC_INDEX = 8;
+
+ case AUTH_TAG_LENGTH = 16;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/Packet.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/Packet.php
new file mode 100644
index 0000000..9035f91
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/Packet.php
@@ -0,0 +1,404 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Client;
+
+use Discord\Voice\ByteBuffer\Buffer;
+use Discord\Voice\ByteBuffer\FormatPackEnum;
+use Discord\Voice\Exceptions\Libraries\LibSodiumNotFoundException;
+
+/**
+ * A voice packet received from Discord.
+ *
+ * Huge thanks to Austin and Michael from JDA for the constants and audio
+ * packets. Check out their repo:
+ * https://github.com/DV8FromTheWorld/JDA
+ *
+ * @since 10.19.0
+ */
+final class Packet
+{
+ /**
+ * The audio header, in binary, containing the version, flags, sequence, timestamp, and SSRC.
+ */
+ protected string $header;
+
+ /**
+ * The buffer containing the voice packet.
+ *
+ * @deprecated
+ */
+ protected Buffer $buffer;
+
+ /**
+ * The version and flags.
+ */
+ public ?string $versionPlusFlags;
+
+ /**
+ * The payload type.
+ */
+ public ?string $payloadType;
+
+ /**
+ * The encrypted audio.
+ */
+ public ?string $encryptedAudio;
+
+ /**
+ * The decrypted audio.
+ */
+ public null|false|string $decryptedAudio;
+
+ /**
+ * The secret key.
+ */
+ public ?string $secretKey;
+
+ /**
+ * The raw data.
+ */
+ protected string $rawData;
+
+ /**
+ * Current packet header size. May differ depending on the RTP header.
+ */
+ protected int $headerSize;
+
+ /**
+ * Constructs the voice packet.
+ *
+ * @param string $data The Opus data to encode.
+ * @param int $ssrc The client SSRC value.
+ * @param int $seq The packet sequence.
+ * @param int $timestamp The packet timestamp.
+ * @param bool $encryption Whether the packet should be encrypted.
+ * @param string|null $key The encryption key.
+ */
+ public function __construct(
+ ?string $data = null,
+ public ?int $ssrc = null,
+ public ?int $seq = null,
+ public ?int $timestamp = null,
+ bool $decrypt = true,
+ protected ?string $key = null
+ ) {
+ if (! function_exists('sodium_crypto_secretbox')) {
+ throw new LibSodiumNotFoundException('libsodium-php could not be found.');
+ }
+
+ if ($decrypt) {
+ $this->unpack($data);
+ $this->decrypt();
+ } else {
+ $this->decryptedAudio = $data;
+ $this->header = $this->buildHeader()->__toString();
+ $this->encrypt();
+ }
+ }
+
+ /**
+ * Unpacks the voice message into an array.
+ *
+ * C1 (unsigned char) | Version + Flags | 1 bytes | Single byte value of 0x80
+ * C1 (unsigned char) | Payload Type | 1 bytes | Single byte value of 0x78
+ * n (Unsigned short (big endian)) | Sequence | 2 bytes
+ * I (Unsigned integer (big endian)) | Timestamp | 4 bytes
+ * I (Unsigned integer (big endian)) | SSRC | 4 bytes
+ * a* (string) | Encrypted audio | n bytes | Binary data of the encrypted audio.
+ *
+ * @see https://discord.com/developers/docs/topics/voice-connections#transport-encryption-modes-voice-packet-structure
+ * @see https://www.php.net/manual/en/function.unpack.php
+ * @see https://www.php.net/manual/en/function.pack.php For the formats
+ */
+ public function unpack(string $message): self
+ {
+ $byteHeader = $this->setHeader($message);
+
+ if (! $byteHeader) {
+ //$this->log->warning('Failed to unpack voice packet Header.', ['message' => $message]);
+ return $this;
+ }
+
+ $byteData = substr(
+ $message,
+ HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value,
+ strlen($message) - HeaderValuesEnum::AUTH_TAG_LENGTH->value - HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value
+ );
+
+ $unpackedMessage = unpack('Cfirst/Csecond/nseq/Ntimestamp/Nssrc', $byteHeader);
+
+ if (! $unpackedMessage) {
+ //$this->log->warning('Failed to unpack voice packet.', ['message' => $message]);
+ return $this;
+ }
+
+ $this->rawData = $message;
+ $this->header = $byteHeader;
+ $this->encryptedAudio = $byteData;
+
+ $this->ssrc = $unpackedMessage['ssrc'];
+ $this->seq = $unpackedMessage['seq'];
+ $this->timestamp = $unpackedMessage['timestamp'];
+ $this->payloadType = $unpackedMessage['payload_type'] ?? null;
+ $this->versionPlusFlags = $unpackedMessage['version_and_flags'] ?? null;
+
+ return $this;
+ }
+
+ /**
+ * Decrypts the voice message.
+ */
+ public function decrypt(?string $message = null): string|false|null
+ {
+ if (! $message) {
+ $message = $this->rawData ?? null;
+ }
+
+ if (empty($message)) {
+ // throw error here
+ return null;
+ }
+
+ // total message length
+ $len = strlen($message);
+
+ // 2. Extract the header
+ $header = $this->getHeader();
+ if (! $header) {
+ return false;
+ }
+
+ // 3. Extract the nonce
+ $nonce = substr($message, $len - HeaderValuesEnum::TIMESTAMP_OR_NONCE_INDEX->value, HeaderValuesEnum::TIMESTAMP_OR_NONCE_INDEX->value);
+ // 4. Pad the nonce to 12 bytes
+ $nonceBuffer = str_pad($nonce, SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES, "\0", STR_PAD_RIGHT);
+
+ // 5. Extract the ciphertext and auth tag
+ // The message: [header][ciphertext][auth tag][nonce]
+ // The size of the ciphertext is: total - headerSize - 16 (auth tag) - 4 (nonce)
+ $encryptedLength = $len - $this->headerSize - HeaderValuesEnum::AUTH_TAG_LENGTH->value - HeaderValuesEnum::TIMESTAMP_OR_NONCE_INDEX->value;
+ $cipherText = substr($message, $this->headerSize, $encryptedLength);
+ $authTag = substr($message, $this->headerSize + $encryptedLength, HeaderValuesEnum::AUTH_TAG_LENGTH->value);
+
+ // Concatenate the ciphertext and the auth tag
+ $combined = "$cipherText$authTag";
+
+ $resultMessage = null;
+
+ try {
+ // Decrypt the message
+ $resultMessage = sodium_crypto_aead_aes256gcm_decrypt(
+ $combined,
+ $header,
+ $nonceBuffer,
+ $this->key
+ );
+
+ // If decryption fails, log the error and return
+ // Most of the time, the length is 20 bytes either for a ping, or an empty voice/udp packet
+ if ($resultMessage === false && strlen($cipherText) !== 20) {
+ //$this->log->warning('Failed to decode voice packet.', ['ssrc' => $this->ssrc]);
+ return false;
+ }
+ // Check if the message contains an extension and remove it
+ elseif (substr($message, 12, 2) === "\xBE\xDE") {
+ // Reads the 2 bytes after the extension identifier to get the extension length
+ $extLengthData = substr($message, 14, 2);
+ $headerExtensionLength = unpack('n', $extLengthData)[1];
+
+ // Remove 4 * headerExtensionLength bytes from the beginning of the decrypted result
+ $resultMessage = substr($resultMessage, 4 * $headerExtensionLength);
+ }
+ } catch (\Throwable $e) {
+ //$this->log->error('Exception occurred when decoding voice packet: ' . $e->getMessage());
+ //$this->log->error('Trace: ' . $e->getTraceAsString());
+ return false;
+ } finally {
+ return $this->decryptedAudio = $resultMessage;
+ }
+ }
+
+ public function encrypt()
+ {
+ $header = $this->getHeader();
+
+ // pad nonce to 12 bytes for AES 256 GCM
+ $nonce = pack('V', $this->seq - 1);
+ $paddedNonce = str_pad($nonce, SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES, "\0", STR_PAD_RIGHT);
+
+ // encrypt the audio
+ $this->encryptedAudio = sodium_crypto_aead_aes256gcm_encrypt($this->decryptedAudio, $header, $paddedNonce, $this->key);
+
+ // set the raw encrypted data with header prepended and nonce appended
+ $this->rawData = $header.$this->encryptedAudio.$nonce;
+ }
+
+ /**
+ * Initilizes the buffer with no encryption.
+ *
+ * @deprecated
+ */
+ protected function initBufferNoEncryption(string $data): void
+ {
+ $data = (string) $data;
+ $header = $this->buildHeader();
+
+ $this->buffer = Buffer::make(strlen((string) $header) + strlen($data))
+ ->write((string) $header, 0)
+ ->write($data, 12);
+ }
+
+ /**
+ * Initilizes the buffer with encryption.
+ */
+ protected function initBufferEncryption(string $data, string $key): void
+ {
+ $data = (string) $data;
+ $header = $this->buildHeader();
+ $nonce = new Buffer(24);
+ $nonce->write((string) $header, 0);
+
+ $data = \sodium_crypto_secretbox($data, (string) $nonce, $key);
+
+ $this->buffer = new Buffer(strlen((string) $header) + strlen($data));
+ $this->buffer->write((string) $header, 0);
+ $this->buffer->write($data, 12);
+ }
+
+ /**
+ * Builds the header.
+ */
+ protected function buildHeader(): Buffer
+ {
+ $header = new Buffer(HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value);
+ $header[HeaderValuesEnum::RTP_VERSION_PAD_EXTEND_INDEX->value] = pack(FormatPackEnum::C->value, HeaderValuesEnum::RTP_VERSION_PAD_EXTEND->value);
+ $header[HeaderValuesEnum::RTP_PAYLOAD_INDEX->value] = pack(FormatPackEnum::C->value, HeaderValuesEnum::RTP_PAYLOAD_TYPE->value);
+
+ return $header->writeShort($this->seq, HeaderValuesEnum::SEQ_INDEX->value)
+ ->writeUInt32BE($this->timestamp, HeaderValuesEnum::TIMESTAMP_OR_NONCE_INDEX->value)
+ ->writeUInt32BE($this->ssrc, HeaderValuesEnum::SSRC_INDEX->value);
+ }
+
+ /**
+ * Sets the header.
+ * If no message is provided, it will use the raw data of the packet.
+ */
+ public function setHeader(?string $message = null): ?string
+ {
+ if (null === $message) {
+ $message = $this->rawData ?? null;
+ }
+
+ if (empty($message)) {
+ // throw error here
+ return null;
+ }
+
+ $this->headerSize = HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value;
+ $firstByte = ord($message[0]);
+ if (($firstByte >> 4) & 0x01) {
+ $this->headerSize += 4;
+ }
+
+ return substr($message, 0, $this->headerSize);
+ }
+
+ /**
+ * Returns the header.
+ */
+ public function getHeader(): ?string
+ {
+ return $this->header ?? null;
+ }
+
+ /**
+ * Returns the sequence.
+ */
+ public function getSequence(): int
+ {
+ return $this->seq;
+ }
+
+ /**
+ * Returns the timestamp.
+ */
+ public function getTimestamp(): int
+ {
+ return $this->timestamp;
+ }
+
+ /**
+ * Returns the SSRC.
+ */
+ public function getSSRC(): int
+ {
+ return $this->ssrc;
+ }
+
+ /**
+ * Returns the data.
+ */
+ public function getData(): string
+ {
+ return $this->buffer->read(
+ HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value,
+ strlen((string) $this->buffer) - HeaderValuesEnum::RTP_HEADER_OR_NONCE_LENGTH->value
+ );
+ }
+
+ /**
+ * Creates a voice packet from data sent from Discord.
+ */
+ public static function make(string $data): self
+ {
+ $n = new self('', 0, 0, 0);
+ $buff = new Buffer($data);
+ $n->setBuffer($buff);
+ unset($buff);
+
+ return $n;
+ }
+
+ /**
+ * Sets the buffer.
+ */
+ public function setBuffer(Buffer $buffer): self
+ {
+ $this->buffer = $buffer;
+
+ $this->seq = $this->buffer->readShort(HeaderValuesEnum::SEQ_INDEX->value);
+ $this->timestamp = $this->buffer->readUInt(HeaderValuesEnum::TIMESTAMP_OR_NONCE_INDEX->value);
+ $this->ssrc = $this->buffer->readUInt(HeaderValuesEnum::SSRC_INDEX->value);
+
+ return $this;
+ }
+
+ /**
+ * Retrieves the decrypted audio data.
+ * Will return null if the audio data is not decrypted and false on error.
+ */
+ public function getAudioData(): string|false|null
+ {
+ return $this->decryptedAudio ?? null;
+ }
+
+ /**
+ * Retrieves the encrypted audio data with header, ready for sending.
+ * Will return null if the audio data is not encrypted.
+ */
+ public function getEncryptedMessage(): string|null
+ {
+ return $this->rawData ?? null;
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/UDP.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/UDP.php
new file mode 100644
index 0000000..80bd216
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/UDP.php
@@ -0,0 +1,313 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Client;
+
+use Discord\Voice\ByteBuffer\Buffer;
+use Discord\WebSockets\Op;
+use Psr\Log\LoggerInterface;
+use React\EventLoop\TimerInterface;
+use React\Datagram\Socket;
+use React\EventLoop\LoopInterface;
+
+/**
+ * Handles the UDP connection & events for Discord voice.
+ * This class manages the UDP socket for sending and receiving audio data,
+ * handling heartbeats, and managing the voice connection state.
+ *
+ * @since 10.19.0
+ */
+final class UDP extends Socket
+{
+ /**
+ * The Parent Voice WebSocket Client.
+ */
+ public WS $ws;
+
+ /**
+ * Silence Frame Remain Count.
+ */
+ public int $silenceRemaining = 5;
+
+ /**
+ * The Opus Silence Frame.
+ */
+ public const string SILENCE_FRAME = "\0xF8\0xFF\0xFE";
+
+ /**
+ * The stream time of the last packet.
+ */
+ public int $streamTime = 0;
+
+ /**
+ * Current heartbeat timer.
+ */
+ public ?TimerInterface $heartbeat = null;
+
+ /**
+ * Heartbeat interval in milliseconds.
+ * The interval at which the heartbeat is sent.
+ */
+ public ?int $hbInterval = null;
+
+ /**
+ * Heartbeat sequence number.
+ * This is used to keep track of the heartbeat messages sent.
+ */
+ protected int $hbSequence = 0;
+
+ /**
+ * The IP address of the UDP server.
+ */
+ public string $ip;
+
+ /**
+ * The port of the UDP server.
+ */
+ public int $port;
+
+ /**
+ * The SSRC identifier.
+ * This is used to identify the source of the audio stream.
+ */
+ public null|string|int $ssrc;
+
+ /**
+ * @param \React\EventLoop\LoopInterface $loop
+ * @param resource $socket
+ * @param null|Buffer $buffer
+ * @param null|WS $ws
+ */
+ public function __construct($loop, $socket, $buffer = null, ?WS $ws = null)
+ {
+ parent::__construct($loop, $socket, $buffer);
+
+ if ($ws !== null) {
+ $this->ws = $ws;
+
+ if (null === $this->hbInterval) {
+ // Set the heartbeat interval to the default value if not set.
+ $this->hbInterval = $this->ws->vc->heartbeatInterval;
+ }
+ }
+ }
+
+ /**
+ * Handles incoming messages from the UDP server.
+ * This is where we handle the audio data received from the server.
+ */
+ public function handleMessages(string $secret): self
+ {
+ return $this->on('message', function (string $message) use ($secret) {
+ if (strlen($message) <= 8) {
+ return null;
+ }
+
+ if ($this->ws->vc->deaf) {
+ return null;
+ }
+
+ return $this->ws->vc->handleAudioData(new Packet($message, key: $secret));
+ });
+ }
+
+ /**
+ * Handles the sending of the SSRC to the server.
+ * This is necessary for the server to know which SSRC we are using.
+ */
+ public function handleSsrcSending(): self
+ {
+ $buffer = new Buffer(74);
+ $buffer[1] = "\x01";
+ $buffer[3] = "\x46";
+ $buffer->writeUInt32BE($this->ws->vc->ssrc, 4);
+ $this->getLoop()->addTimer(0.1, fn () => $this->send($buffer->__toString()));
+
+ return $this;
+ }
+
+ /**
+ * Handles the heartbeat for the UDP client.
+ * To keep the connection open and responsive.
+ */
+ public function handleHeartbeat(): self
+ {
+ if (empty($this->hbInterval)) {
+ $this->hbInterval = $this->ws->vc->heartbeatInterval;
+ }
+
+ if (null === $this->getLoop()) {
+ $this->getLogger()->error('No event loop found. Cannot handle heartbeat.');
+
+ return $this;
+ }
+
+ $this->heartbeat = $this->getLoop()->addPeriodicTimer(
+ $this->hbInterval / 1000,
+ function (): void {
+ $buffer = new Buffer(9);
+ $buffer[0] = 0xC9;
+ $buffer->writeUInt64LE($this->hbSequence, 1);
+ ++$this->hbSequence;
+
+ $this->send($buffer->__toString());
+ $this->ws->vc->emit('udp-heartbeat', []);
+
+ $this->getLogger()->debug('sent UDP heartbeat');
+ }
+ );
+
+ return $this;
+ }
+
+ /**
+ * Decodes the first UDP message received from the server.
+ * To discover which IP and port we should connect to.
+ *
+ * @see https://discord.com/developers/docs/topics/voice-connections#ip-discovery
+ */
+ public function decodeOnce(): self
+ {
+ return $this->once('message', function (string $message) {
+ /**
+ * Unpacks the message into an array.
+ *
+ * C2 (unsigned char) | Type | 2 bytes | Values 0x1 and 0x2 indicate request and response, respectively
+ * n (unsigned short) | Length | 2 bytes | Length of the following data
+ * I (unsigned int) | SSRC | 4 bytes | The SSRC of the sender
+ * A64 (string) | Address | 64 bytes | The IP address of the sender
+ * n (unsigned short) | Port | 2 bytes | The port of the sender
+ *
+ * @see https://discord.com/developers/docs/topics/voice-connections#ip-discovery
+ * @see https://www.php.net/manual/en/function.unpack.php
+ * @see https://www.php.net/manual/en/function.pack.php For the formats
+ */
+ $unpackedMessageArray = \unpack('C2Type/nLength/NSSRC/A64Address/nPort', $message);
+
+ $this->ws->vc->ssrc = $unpackedMessageArray['SSRC'];
+ $ip = $unpackedMessageArray['Address'];
+ $port = $unpackedMessageArray['Port'];
+
+ $this->getLogger()->debug('received our IP and port', ['ip' => $ip, 'port' => $port]);
+
+ $this->ws->send([
+ 'op' => Op::VOICE_SELECT_PROTOCOL,
+ 'd' => [
+ 'protocol' => 'udp',
+ 'data' => [
+ 'address' => $ip,
+ 'port' => $port,
+ 'mode' => $this->ws->mode,
+ ],
+ ],
+ ]);
+ });
+ }
+
+ /**
+ * Handles errors that occur during UDP communication.
+ */
+ public function handleErrors(): self
+ {
+ return $this->on('error', function (\Throwable $e): void {
+ $this->getLogger()->error('UDP error', ['e' => $e->getMessage()]);
+ $this->ws->vc->emit('udp-error', [$e]);
+ });
+ }
+
+ /**
+ * Insert 5 frames of silence.
+ *
+ * @link https://discord.com/developers/docs/topics/voice-connections#voice-data-interpolation
+ */
+ public function insertSilence(): void
+ {
+ while (--$this->silenceRemaining > 0) {
+ $this->sendBuffer(self::SILENCE_FRAME);
+ }
+ }
+
+ /**
+ * Sends a buffer to the UDP socket.
+ */
+ public function sendBuffer(string $data): void
+ {
+ if (! $this->ws->vc->ready) {
+ return;
+ }
+
+ $packet = new Packet(
+ $data,
+ $this->ws->vc->ssrc,
+ $this->ws->vc->seq,
+ $this->ws->vc->timestamp,
+ false,
+ $this->ws->secretKey,
+ );
+ $this->send($packet->getEncryptedMessage());
+
+ $this->streamTime = (int) microtime(true);
+
+ $this->ws->vc->emit('packet-sent', [$packet]);
+ }
+
+ /**
+ * Closes the UDP client and cancels the heartbeat timer.
+ */
+ public function close(): void
+ {
+ if ($this->heartbeat) {
+ $this->getLoop()->cancelTimer($this->heartbeat);
+ $this->heartbeat = null;
+ }
+
+ parent::close();
+ }
+
+ /**
+ * Whether the socket is already closed.
+ */
+ public function isClosed(): bool
+ {
+ return ! $this->socket;
+ }
+
+ /**
+ * Refreshes the silence frames.
+ */
+ public function refreshSilenceFrames(): void
+ {
+ if (! $this->ws->vc->paused) {
+ // If the voice client is paused, we don't need to refresh the silence frames.
+ return;
+ }
+
+ $this->silenceRemaining = 5;
+ }
+
+ /**
+ * Gets the logger instance.
+ */
+ private function getLogger(): LoggerInterface
+ {
+ return $this->ws->vc->discord->getLogger();
+ }
+
+ /**
+ * Gets the event loop instance.
+ */
+ private function getLoop(): LoopInterface
+ {
+ return $this->ws->vc->discord->getLoop();
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/User.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/User.php
new file mode 100644
index 0000000..97a7ac8
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/User.php
@@ -0,0 +1,36 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Client;
+
+use Discord\Discord;
+use Discord\Voice\ReceiveStream;
+use Discord\Voice\Speaking;
+use Discord\Voice\VoiceClient;
+use React\ChildProcess\Process;
+
+/**
+ * @since 10.19.0
+ */
+final class User
+{
+ public function __construct(
+ protected Discord $discord,
+ protected VoiceClient $voiceClient,
+ protected int $ssrc,
+ protected Process $decoder,
+ protected ReceiveStream $stream,
+ protected ?Speaking $part = null,
+ ) {
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/WS.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/WS.php
new file mode 100644
index 0000000..9635a85
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Client/WS.php
@@ -0,0 +1,657 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Client;
+
+use Discord\Discord;
+use Discord\Factory\SocketFactory;
+use Discord\Parts\Voice\UserConnected;
+use Discord\Voice\Any;
+use Discord\Voice\Client;
+use Discord\Voice\Flags;
+use Discord\Voice\Hello;
+use Discord\Voice\Platform;
+use Discord\Voice\Ready;
+use Discord\Voice\Resumed;
+use Discord\Voice\SessionDescription;
+use Discord\Voice\Speaking;
+use Discord\WebSockets\Op;
+use Discord\WebSockets\Payload;
+use Discord\WebSockets\VoicePayload;
+use Ratchet\Client\Connector;
+use Ratchet\Client\WebSocket;
+use Ratchet\RFC6455\Messaging\Message;
+use React\EventLoop\TimerInterface;
+use React\Promise\PromiseInterface;
+
+/**
+ * Handles the Discord voice WebSocket connection.
+ *
+ * This class manages the WebSocket connection to the Discord voice gateway,
+ * handling events, sending messages, and managing the voice connection state.
+ *
+ * @since 10.19.0
+ */
+final class WS
+{
+ /**
+ * The maximum DAVE protocol version supported.
+ */
+ public const MAX_DAVE_PROTOCOL_VERSION = 0;
+
+ /**
+ * Dispatch table mapping Discord Voice Gateway opcodes to handler methods.
+ *
+ * @var array Method name indexed by opcode constant.
+ */
+ public const VOICE_OP_HANDLERS = [
+ Op::VOICE_READY => 'handleReady',
+ Op::VOICE_SESSION_DESCRIPTION => 'handleSessionDescription',
+ Op::VOICE_SPEAKING => 'handleSpeaking',
+ Op::VOICE_HEARTBEAT_ACK => 'handleHeartbeatAck',
+ Op::VOICE_HELLO => 'handleHello',
+ Op::VOICE_RESUMED => 'handleResumed',
+ Op::VOICE_CLIENT_CONNECT => 'handleClientConnect',
+ Op::VOICE_CLIENT_DISCONNECT => 'handleClientDisconnect',
+ Op::VOICE_CLIENT_UNKNOWN_15 => 'handleAny',
+ Op::VOICE_CLIENT_UNKNOWN_18 => 'handleFlags',
+ Op::VOICE_CLIENT_PLATFORM => 'handlePlatform',
+ Op::VOICE_DAVE_PREPARE_TRANSITION => 'handleDavePrepareTransition',
+ Op::VOICE_DAVE_EXECUTE_TRANSITION => 'handleDaveExecuteTransition',
+ Op::VOICE_DAVE_TRANSITION_READY => 'handleDaveTransitionReady',
+ Op::VOICE_DAVE_PREPARE_EPOCH => 'handleDavePrepareEpoch',
+ Op::VOICE_DAVE_MLS_EXTERNAL_SENDER => 'handleDaveMlsExternalSender',
+ Op::VOICE_DAVE_MLS_KEY_PACKAGE => 'handleDaveMlsKeyPackage',
+ Op::VOICE_DAVE_MLS_PROPOSALS => 'handleDaveMlsProposals',
+ Op::VOICE_DAVE_MLS_COMMIT_WELCOME => 'handleDaveMlsCommitWelcome',
+ Op::VOICE_DAVE_MLS_ANNOUNCE_COMMIT_TRANSITION => 'handleDaveMlsAnnounceCommitTransition',
+ Op::VOICE_DAVE_MLS_WELCOME => 'handleDaveMlsWelcome',
+ Op::VOICE_DAVE_MLS_INVALID_COMMIT_WELCOME => 'handleDaveMlsInvalidCommitWelcome',
+
+ Op::CLOSE_VOICE_DISCONNECTED => 'handleCloseVoiceDisconnected',
+ ];
+
+ /**
+ * The SocketFactory instance for creating UDP sockets.
+ */
+ protected SocketFactory $udpfac;
+
+ /**
+ * The WebSocket instance for the voice connection.
+ */
+ protected WebSocket $socket;
+
+ /**
+ * The Discord voice gateway version.
+ *
+ * @see https://discord.com/developers/docs/topics/voice-connections#voice-gateway-versioning-gateway-versions
+ */
+ protected static $version = 8;
+
+ /**
+ * The Voice WebSocket mode.
+ *
+ * @link https://discord.com/developers/docs/topics/voice-connections#transport-encryption-modes
+ */
+ public string $mode = 'aead_aes256_gcm_rtpsize';
+
+ /**
+ * The secret key used for encrypting voice.
+ */
+ public ?string $secretKey;
+
+ /**
+ * The raw secret key.
+ */
+ public ?array $rawKey;
+
+ /**
+ * The SSRC identifier for the voice connection client.
+ */
+ public null|string|int $ssrc;
+
+ /**
+ * Indicates whether the login frame has been sent.
+ */
+ private bool $sentLoginFrame = false;
+
+ /**
+ * The heartbeat timer for the voice connection.
+ */
+ protected TimerInterface $heartbeat;
+
+ /**
+ * The heartbeat interval for the voice connection.
+ */
+ protected $hbInterval;
+
+ /**
+ * The heartbeat sequence number.
+ *
+ * This is used to track the sequence of heartbeat messages sent to the voice gateway.
+ */
+ protected int $hbSequence = 0;
+
+ /**
+ * The WebSocket connection for the voice client.
+ *
+ * This is used to send and receive messages over the WebSocket connection.
+ */
+ public function __construct(
+ public Client $vc,
+ protected ?Discord $discord = null,
+ public ?array $data = [],
+ ) {
+ $this->data ??= $this->vc->data;
+ $this->discord ??= $this->vc->discord;
+
+ if (! isset($this->data['endpoint'])) {
+ throw new \InvalidArgumentException('Endpoint is required for the voice WebSocket connection.');
+ }
+
+ $this->discord->logger->debug('Creating new voice websocket', ['endpoint' => $this->data['endpoint']]);
+
+ $f = new Connector();
+
+ /** @var PromiseInterface */
+ $f('wss://'.$this->data['endpoint'].'?v='.self::$version)->then(
+ fn (WebSocket $ws) => $this->handleConnection($ws),
+ fn (\Throwable $e) => $this->discord->logger->error(
+ 'Failed to connect to voice gateway: {error}',
+ ['error' => $e->getMessage()]
+ ) && $this->vc->emit('error', arguments: [$e])
+ );
+ }
+
+ /**
+ * Creates a new instance of the WS class.
+ *
+ * @param Client $vc
+ * @param null|Discord $discord
+ * @param null|array $data
+ *
+ * @return WS
+ */
+ public static function make(Client $vc, ?Discord $discord = null, ?array $data = null): self
+ {
+ return new self($vc, $discord, $data);
+ }
+
+ /**
+ * Handles a WebSocket connection.
+ */
+ public function handleConnection(WebSocket $ws): void
+ {
+ $this->discord->logger->debug('connected to voice websocket');
+
+ $this->udpfac = new SocketFactory(ws: $this);
+
+ $this->socket = $this->vc->ws = $ws;
+
+ $ws->on('message', function (Message $message): void {
+ if (($data = json_decode($message->getPayload(), true)) === false) {
+ return;
+ }
+ $data = Payload::fromArray($data);
+
+ $this->vc->emit('ws-message', [$message, $this->vc]);
+
+ if (isset(self::VOICE_OP_HANDLERS[$data->op])) {
+ $handler = self::VOICE_OP_HANDLERS[$data->op];
+ $this->$handler($data);
+ } else {
+ $this->discord->getLogger()->debug('unknown voice op', ['op' => $data->op]);
+ $this->handleUndocumented($data);
+ }
+ });
+
+ $ws->on('error', function ($e): void {
+ $this->discord->logger->error('error with voice websocket', ['e' => $e->getMessage()]);
+ $this->vc->emit('ws-error', [$e]);
+ });
+
+ $ws->on('close', [$this, 'handleClose']);
+
+ if (! $this->sentLoginFrame) {
+ $this->handleSendingOfLoginFrame();
+ $this->sentLoginFrame = true;
+ } elseif (isset(
+ $this->data['token'],
+ $this->data['seq'],
+ $this->discord->voice_sessions[$this->vc->channel->guild_id]
+ )) {
+ $this->handleResume();
+ } else {
+ $this->discord->getLogger()->debug('existing voice session or data not found, re-sending identify', ['guild_id' => $this->vc->channel->guild_id]);
+ $this->handleSendingOfLoginFrame();
+ }
+ }
+
+ /**
+ * Sends a message to the voice websocket.
+ */
+ public function send(VoicePayload|array $data): void
+ {
+ $this->socket->send(json_encode($data));
+ }
+
+ /**
+ * Handles the "ready" event for the voice client, initializing UDP connection and heartbeat.
+ *
+ * @param Payload $data The data object containing voice server connection details:
+ * - $data->d['ssrc']: The synchronization source identifier.
+ * - $data->d['ip']: The IP address for the UDP connection.
+ * - $data->d['port']: The port for the UDP connection.
+ * - $data->d['modes']: Supported encryption modes.
+ */
+ protected function handleReady(Payload $data): void
+ {
+ /** @var Ready */
+ $ready = $this->discord->factory(Ready::class, (array) $data->d, true);
+
+ $this->vc->ssrc = $ready->ssrc;
+ $this->discord->logger->debug('received voice ready packet', ['data' => json_decode(json_encode($data->d), true)]);
+
+ /** @var PromiseInterface */
+ $this->udpfac->createClient("{$ready->ip}:".$ready->port)->then(function (UDP $client) use ($ready): void {
+ $this->vc->udp = $client;
+ $client->handleSsrcSending()
+ ->handleHeartbeat()
+ ->handleErrors()
+ ->decodeOnce();
+
+ $client->ip = $ready->ip;
+ $client->port = $ready->port;
+ $client->ssrc = $ready->ssrc;
+ }, function (\Throwable $e): void {
+ $this->discord->logger->error('error while connecting to udp', ['e' => $e->getMessage()]);
+ $this->vc->emit('error', [$e]);
+ });
+ }
+
+ /**
+ * Handles the session description packet received from the Discord voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handleSessionDescription(Payload $data): void
+ {
+ /** @var SessionDescription */
+ $sd = $this->discord->factory(SessionDescription::class, (array) $data->d, true);
+
+ $this->vc->ready = true;
+ $this->mode = $sd->mode === $this->mode ? $this->mode : 'aead_aes256_gcm_rtpsize';
+ $this->rawKey = $data->d['secret_key'];
+ $this->secretKey = $sd->secret_key;
+
+ $this->discord->logger->debug('received description packet, vc ready', ['data' => $sd->__debugInfo()]);
+
+ if (! $this->vc->reconnecting) {
+ $this->vc->emit('ready', [$this->vc]);
+ } else {
+ $this->vc->reconnecting = false;
+ $this->vc->emit('resumed', [$this->vc]);
+ # TODO: check if this can fix the reconnect issue
+ //$this->vc->emit('ready', [$this->vc]);
+ }
+
+ if (! $this->vc->deaf && $this->secretKey) {
+ $this->vc->udp->handleMessages($this->secretKey);
+ }
+ }
+
+ /**
+ * Handles the speaking state of a user.
+ *
+ * @param Payload $data The data object received from the WebSocket.
+ */
+ protected function handleSpeaking(Payload $data): void
+ {
+ /** @var Speaking */
+ $speaking = $this->discord->factory(Speaking::class, (array) $data->d, true);
+
+ $this->discord->logger->debug('received speaking packet', ['data' => json_decode(json_encode($data->d), true)]);
+ $this->vc->speakingStatus[$speaking->user_id] = $speaking;
+ $this->vc->emit('speaking', [$speaking->speaking, $speaking->user_id, $this->vc]);
+ $this->vc->emit("speaking.{$speaking->user_id}", [$speaking->speaking, $this->vc]);
+ }
+
+ /**
+ * Handles the heartbeat acknowledgement from the voice WebSocket connection.
+ *
+ * @param Payload $data
+ */
+ public function handleHeartbeatAck(Payload $data): void
+ {
+ $diff = (microtime(true) - $data->d['t']) * 1000;
+
+ $this->discord->logger->debug('received heartbeat ack', ['response_time' => $diff]);
+ $this->vc->emit('ws-ping', [$diff]);
+ $this->vc->emit('ws-heartbeat-ack', [$data->d['t']]);
+ }
+
+ /**
+ * Handles the "Hello" event from the Discord voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handleHello(Payload $data): void
+ {
+ /** @var Hello */
+ $hello = $this->discord->factory(Hello::class, (array) $data->d, true);
+
+ $this->hbInterval = $this->vc->heartbeatInterval = $hello->heartbeat_interval;
+ $this->sendHeartbeat();
+ $this->heartbeat = $this->discord->loop->addPeriodicTimer(
+ $this->hbInterval / 1000,
+ fn () => $this->sendHeartbeat()
+ );
+ }
+
+ /**
+ * Handles the 'resumed' event for the voice client.
+ *
+ * @param Payload $data
+ */
+ protected function handleResumed(Payload $data): void
+ {
+ /** @var Resumed */
+ $resumed = $this->discord->factory(Resumed::class, (array) $data->d, true);
+ $this->discord->getLogger()->debug('received resumed packet', ['data' => $resumed]);
+ }
+
+ /**
+ * Handles the event when a client connects to the voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handleClientConnect(Payload $data): void
+ {
+ $this->discord->getLogger()->debug('received client connect packet', ['data' => $data]);
+ // "d" contains an array with ['user_ids' => array]
+ $this->vc->users = array_map(fn (int $userId) => $this->discord->getFactory()->part(UserConnected::class, ['user_id' => $userId]), $data->d['user_ids']);
+ }
+
+ /**
+ * Handles the event when a client disconnects from the voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handleClientDisconnect(Payload $data): void
+ {
+ $this->discord->logger->debug('received client disconnected packet', ['data' => $data]);
+ unset($this->vc->clientsConnected[$data->d['user_id']]);
+ }
+
+ /**
+ * Handles the any event from the voice server.
+ *
+ * @param Payload $data
+ */
+ public function handleAny(Payload $data): void
+ {
+ $any = $this->discord->factory(Any::class, (array) $data->d, true);
+
+ $this->discord->logger->debug('received any packet', ['data' => $any->__debugInfo()]);
+ }
+
+ /**
+ * Handles the flags event from the voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handleFlags(Payload $data): void
+ {
+ $flags = $this->discord->factory(Flags::class, (array) $data->d, true);
+
+ $this->discord->logger->debug('received flags packet', ['data' => $flags->__debugInfo()]);
+ }
+
+ /**
+ * Handles the platform event from the voice server.
+ *
+ * @param Payload $data
+ */
+ protected function handlePlatform(Payload $data): void
+ {
+ $platform = $this->discord->factory(Platform::class, (array) $data->d, true);
+
+ $this->discord->logger->debug('received platform packet', ['data' => $platform->__debugInfo()]);
+ }
+
+ /**
+ * Handles undocumented voice opcodes not intended for use by bots.
+ *
+ * @param Payload $data
+ */
+ protected function handleUndocumented(Payload $data): void
+ {
+ }
+
+ protected function handleDavePrepareTransition($data): void
+ {
+ $this->discord->logger->debug('DAVE Prepare Transition', ['data' => $data]);
+ // Prepare local state necessary to perform the transition
+ $this->send(VoicePayload::new(
+ Op::VOICE_DAVE_TRANSITION_READY,
+ ['transition_id' => $data->d['transition_id']],
+ ));
+ }
+
+ protected function handleDaveExecuteTransition($data): void
+ {
+ $this->discord->logger->debug('DAVE Execute Transition', ['data' => $data]);
+ // Execute the transition
+ // Update local state to reflect the new protocol context
+ }
+
+ protected function handleDaveTransitionReady($data): void
+ {
+ $this->discord->logger->debug('DAVE Transition Ready', ['data' => $data]);
+ // Handle transition ready state
+ }
+
+ protected function handleDavePrepareEpoch($data): void
+ {
+ $this->discord->logger->debug('DAVE Prepare Epoch', ['data' => $data]);
+ // Prepare local MLS group with parameters appropriate for the DAVE protocol version
+ $this->send(VoicePayload::new(
+ Op::VOICE_DAVE_MLS_KEY_PACKAGE,
+ [
+ 'epoch_id' => $data->d['epoch_id'],
+ //'key_package' => $this->generateKeyPackage(),
+ ],
+ ));
+ }
+
+ protected function handleDaveMlsExternalSender($data): void
+ {
+ $this->discord->logger->debug('DAVE MLS External Sender', ['data' => $data]);
+ // Handle external sender public key and credential
+ }
+
+ protected function handleDaveMlsKeyPackage($data): void
+ {
+ $this->discord->logger->debug('DAVE MLS Key Package', ['data' => $data]);
+ // Handle MLS key package
+ }
+
+ protected function handleDaveMlsProposals($data): void
+ {
+ $this->discord->logger->debug('DAVE MLS Proposals', ['data' => $data]);
+ // Handle MLS proposals
+ $this->send(VoicePayload::new(
+ Op::VOICE_DAVE_MLS_COMMIT_WELCOME,
+ [
+ //'commit' => $this->generateCommit(),
+ //'welcome' => $this->generateWelcome(),
+ ],
+ ));
+ }
+
+ protected function handleDaveMlsCommitWelcome($data): void
+ {
+ $this->discord->logger->debug('DAVE MLS Commit Welcome', ['data' => $data]);
+ // Handle MLS commit and welcome messages
+ }
+
+ protected function handleDaveMlsAnnounceCommitTransition($data)
+ {
+ // Handle MLS announce commit transition
+ $this->discord->logger->debug('DAVE MLS Announce Commit Transition', ['data' => $data]);
+ }
+
+ protected function handleDaveMlsWelcome($data)
+ {
+ // Handle MLS welcome message
+ $this->discord->logger->debug('DAVE MLS Welcome', ['data' => $data]);
+ }
+
+ protected function handleDaveMlsInvalidCommitWelcome($data)
+ {
+ $this->discord->logger->debug('DAVE MLS Invalid Commit Welcome', ['data' => $data]);
+ // Handle invalid commit or welcome message
+ // Reset local group state and generate a new key package
+ $this->send(VoicePayload::new(
+ Op::VOICE_DAVE_MLS_KEY_PACKAGE,
+ [
+ //'key_package' => $this->generateKeyPackage(),
+ ],
+ ));
+ }
+
+ /**
+ * Sends a heartbeat to the voice WebSocket.
+ */
+ public function sendHeartbeat(): void
+ {
+ $this->send(VoicePayload::new(
+ Op::VOICE_HEARTBEAT,
+ [
+ 't' => (int) microtime(true),
+ 'seq_ack' => ++$this->hbSequence,
+ ]
+ ));
+ $this->discord->logger->debug('sending heartbeat');
+ $this->vc->emit('ws-heartbeat', []);
+ }
+
+ /**
+ * Handles the close event of the WebSocket connection.
+ *
+ * @param int $op The opcode of the close event.
+ * @param string $reason The reason for closing the connection.
+ */
+ public function handleClose(int $op, string $reason): void
+ {
+ $this->discord->logger->warning('voice websocket closed', ['op' => $op, 'reason' => $reason]);
+ $this->vc->emit('ws-close', [$op, $reason, $this]);
+
+ $this->vc->clientsConnected = [];
+
+ // Cancel heartbeat timers
+ if (null !== $this->vc->heartbeat) {
+ $this->discord->loop->cancelTimer($this->vc->heartbeat);
+ $this->vc->heartbeat = null;
+ }
+
+ // Close UDP socket.
+ if (isset($this->vc->udp)) {
+ $this->discord->logger->warning('closing UDP client');
+ $this->vc->udp->close();
+ }
+
+ $this->socket->close();
+ $this->discord->voice_sessions[$this->vc->channel->guild_id] = null;
+
+ // Don't reconnect on a critical opcode or if closed by user.
+ if (in_array($op, Op::getCriticalVoiceCloseCodes()) || $this?->vc->userClose) {
+ $this->discord->logger->warning('received critical opcode - not reconnecting', ['op' => $op, 'reason' => $reason]);
+ if ($op === Op::CLOSE_INVALID_SESSION) {
+ $this->discord->logger->debug('sessions', ['voice_sessions' => $this->discord->voice_sessions]);
+ }
+ $this->vc->voice_sessions[$this->vc->channel->guild_id] = null;
+ // prevent race conditions
+ if ($this->vc->ready) {
+ $this->vc->close();
+ }
+
+ return;
+ }
+
+ $this->discord->logger->warning('reconnecting in 2 seconds');
+
+ // Retry connect after 2 seconds
+ $this->discord->loop->addTimer(2, function (): void {
+ $this->vc->reconnecting = true;
+ $this->vc->sentLoginFrame = false;
+ $this->sentLoginFrame = false;
+
+ $this->vc->boot();
+ });
+ }
+
+ /**
+ * Handles sending the login frame to the voice WebSocket.
+ *
+ * This method sends the initial identification payload to the voice gateway
+ * to establish the voice connection.
+ *
+ * @link https://discord.com/developers/docs/topics/voice-connections#establishing-a-voice-websocket-connection-example-voice-identify-payload
+ */
+ public function handleSendingOfLoginFrame(): void
+ {
+ if ($this->sentLoginFrame) {
+ return;
+ }
+
+ $data = [
+ 'server_id' => $this->vc->channel->guild_id,
+ 'user_id' => $this->data['user_id'],
+ 'token' => $this->data['token'],
+ 'max_dave_protocol_version' => self::MAX_DAVE_PROTOCOL_VERSION,
+ ];
+ if (isset($this->discord->voice_sessions[$this->vc->channel->guild_id])) {
+ $this->data['session'] = $this->discord->voice_sessions[$this->vc->channel->guild_id];
+ $data['session_id'] = $this->data['session'];
+ }
+
+ $payload = VoicePayload::new(Op::VOICE_IDENTIFY, $data);
+
+ $this->discord->logger->debug('sending identify', ['packet' => $payload->__debugInfo()]);
+
+ $this->send($payload);
+ $this->sentLoginFrame = true;
+ $this->vc->sentLoginFrame = true;
+ }
+
+ /**
+ * Resumes a previously established voice connection.
+ */
+ protected function handleResume(): void
+ {
+ $payload = Payload::new(
+ Op::VOICE_RESUME,
+ [
+ 'server_id' => $this->vc->channel->guild_id,
+ 'session_id' => $this->discord->voice_sessions[$this->vc->channel->guild_id],
+ 'token' => $this->data['token'],
+ 'seq_ack' => $this->data['seq'],
+ ]
+ );
+
+ $this->discord->logger->debug('sending identify (resume)', ['packet' => $payload->__debugInfo()]);
+
+ $this->send($payload);
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/BufferTimedOutException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/BufferTimedOutException.php
new file mode 100644
index 0000000..df38e9b
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/BufferTimedOutException.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions;
+
+use RuntimeException;
+
+/**
+ * Thrown when reading from a buffer times out.
+ *
+ * @since 10.0.0
+ */
+class BufferTimedOutException extends RuntimeException
+{
+ /**
+ * Create a new buffer timeout exception.
+ */
+ public function __construct()
+ {
+ parent::__construct('Reading from the buffer timed out.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/AudioAlreadyPlayingException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/AudioAlreadyPlayingException.php
new file mode 100644
index 0000000..32227dc
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/AudioAlreadyPlayingException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Channels;
+
+/**
+ * Thrown when the Voice Client is already playing audio.
+ *
+ * @since 10.0.0
+ */
+final class AudioAlreadyPlayingException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'Audio is already playing.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantJoinMoreThanOneChannelException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantJoinMoreThanOneChannelException.php
new file mode 100644
index 0000000..586302c
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantJoinMoreThanOneChannelException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Channels;
+
+/**
+ * Thrown when the selected Channel does not allow voice.
+ *
+ * @since 10.0.0
+ */
+final class CantJoinMoreThanOneChannelException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'You cannot join more than one voice channel per guild/server.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantSpeakInChannelException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantSpeakInChannelException.php
new file mode 100644
index 0000000..c0f036a
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/CantSpeakInChannelException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Channels;
+
+/**
+ * Thrown when the selected Channel does not allow voice.
+ *
+ * @since 10.0.0
+ */
+final class CantSpeakInChannelException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'The current Channel doesn\'t have proper permissions for the Bot to speak in it.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/ChannelMustAllowVoiceException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/ChannelMustAllowVoiceException.php
new file mode 100644
index 0000000..74a2e35
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/ChannelMustAllowVoiceException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Channels;
+
+/**
+ * Thrown when the selected Channel does not allow voice.
+ *
+ * @since 10.0.0
+ */
+final class ChannelMustAllowVoiceException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'Current Channel must allow voice.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/EnterChannelDeniedException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/EnterChannelDeniedException.php
new file mode 100644
index 0000000..bdc6109
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Channels/EnterChannelDeniedException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Channels;
+
+/**
+ * Thrown when the selected Channel does not allow voice.
+ *
+ * @since 10.0.0
+ */
+final class EnterChannelDeniedException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'The current Channel doesn\'t have proper permissions for the Bot to connect to it.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/ClientNotReadyException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/ClientNotReadyException.php
new file mode 100644
index 0000000..757abee
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/ClientNotReadyException.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions;
+
+/**
+ * Thrown when the Voice Client is not ready.
+ *
+ * @since 10.0.0
+ */
+final class ClientNotReadyException extends \RuntimeException
+{
+ public function __construct(?string $message = null)
+ {
+ parent::__construct($message ?? 'Voice Client is not ready.');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/DCANotFoundException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/DCANotFoundException.php
new file mode 100644
index 0000000..3957648
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/DCANotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Libraries;
+
+/**
+ * Thrown when DCA could not be found.
+ *
+ * @since 3.2.0
+ */
+class DCANotFoundException extends \Exception
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/FFmpegNotFoundException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/FFmpegNotFoundException.php
new file mode 100644
index 0000000..1ccb46b
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/FFmpegNotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Libraries;
+
+/**
+ * Thrown when the FFmpeg binary cannot be found in your PATH.
+ *
+ * @since 3.2.0
+ */
+class FFmpegNotFoundException extends \Exception
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/LibSodiumNotFoundException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/LibSodiumNotFoundException.php
new file mode 100644
index 0000000..213f6cd
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/LibSodiumNotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Libraries;
+
+/**
+ * Thrown when libsodium or libsodium-php cannot be found.
+ *
+ * @since 3.2.1
+ */
+class LibSodiumNotFoundException extends \Exception
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OpusNotFoundException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OpusNotFoundException.php
new file mode 100644
index 0000000..e086856
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OpusNotFoundException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Libraries;
+
+/**
+ * Thrown when FFmpeg is not compiled with libopus.
+ *
+ * @since 3.2.0
+ */
+class OpusNotFoundException extends \Exception
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OutdatedDCAException.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OutdatedDCAException.php
new file mode 100644
index 0000000..caea58a
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Exceptions/Libraries/OutdatedDCAException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Exceptions\Libraries;
+
+/**
+ * Thrown when the installed DCA version is outdated.
+ *
+ * @since 3.2.0
+ */
+class OutdatedDCAException extends \Exception
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/Buffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/Buffer.php
new file mode 100644
index 0000000..83479f9
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/Buffer.php
@@ -0,0 +1,212 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers;
+
+use Discord\Voice\Exceptions\BufferTimedOutException;
+use Evenement\EventEmitter;
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @since 6.0.0
+ */
+class Buffer extends EventEmitter implements WritableStreamInterface
+{
+ /**
+ * Internal buffer.
+ *
+ * @var string
+ */
+ protected $buffer = '';
+
+ /**
+ * Array of deferred reads waiting to be resolved.
+ *
+ * @var Deferred[]|int[]
+ */
+ protected $reads = [];
+
+ /**
+ * Whether the buffer has been closed.
+ *
+ * @var bool
+ */
+ protected $closed = false;
+
+ /**
+ * ReactPHP event loop.
+ * Required for timeouts.
+ *
+ * @var LoopInterface
+ */
+ protected $loop;
+
+ public function __construct(?LoopInterface $loop = null)
+ {
+ $this->loop = $loop ?? Loop::get();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function write($data): bool
+ {
+ if ($this->closed) {
+ return false;
+ }
+
+ $this->buffer .= (string) $data;
+
+ foreach ($this->reads as $key => [$deferred, $length]) {
+ if (($output = $this->readRaw($length)) !== false) {
+ $deferred->resolve($output);
+ unset($this->reads[$key]);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Reads from the buffer and returns in a string.
+ * Returns false if there were not enough bytes in the buffer.
+ *
+ * @param int $length Number of bytes to read.
+ *
+ * @return string|bool The bytes read, or false if not enough bytes are present.
+ */
+ protected function readRaw(int $length)
+ {
+ if (strlen($this->buffer) >= $length) {
+ $output = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+
+ return $output;
+ }
+
+ return false;
+ }
+
+ /**
+ * Reads from the buffer and returns a promise.
+ * The promise will resolve when there are enough bytes in the buffer to
+ * read.
+ *
+ * @param int $length Number of bytes to read.
+ * @param string|null $format Format to read the bytes in. See `pack()`.
+ * @param int $timeout Time in milliseconds before the read times out.
+ *
+ * @return PromiseInterface
+ *
+ * @throws \RuntimeException When there is an error unpacking the read bytes.
+ */
+ public function read(int $length, ?string $format = null, ?int $timeout = -1): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ if (($output = $this->readRaw($length)) !== false) {
+ $deferred->resolve($output);
+ } else {
+ $this->reads[] = [$deferred, $length];
+
+ if ($timeout > 0 && $this->loop !== null) {
+ $timer = $this->loop->addTimer($timeout / 1000, function () use ($deferred) {
+ $deferred->reject(new BufferTimedOutException());
+ });
+
+ $deferred->promise()->then(function () use ($timer) {
+ $this->loop->cancelTimer($timer);
+ });
+ } elseif ($timeout === 0) {
+ $deferred->reject(new BufferTimedOutException());
+ }
+ }
+
+ return $deferred->promise()->then(function ($d) use ($format) {
+ if ($format !== null) {
+ $unpacked = unpack($format, $d);
+
+ if ($unpacked === false) {
+ throw new \RuntimeException('Error unpacking buffer.');
+ }
+
+ return reset($unpacked);
+ }
+
+ return $d;
+ });
+ }
+
+ /**
+ * Reads a signed 32-bit integer from the buffer.
+ *
+ * @param int $timeout Time in milliseconds before the read times out.
+ *
+ * @return PromiseInterface
+ *
+ * @throws \RuntimeException When there is an error unpacking the read bytes.
+ */
+ public function readInt32(int $timeout = -1): PromiseInterface
+ {
+ return $this->read(4, 'l', $timeout);
+ }
+
+ /**
+ * Reads a signed 16-bit integer from the buffer.
+ *
+ * @param int $timeout Time in milliseconds before the read times out.
+ *
+ * @return PromiseInterface
+ *
+ * @throws \RuntimeException When there is an error unpacking the read bytes.
+ */
+ public function readInt16(int $timeout = -1): PromiseInterface
+ {
+ return $this->read(2, 'v', $timeout);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isWritable()
+ {
+ return $this->closed;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function end($data = null): void
+ {
+ $this->write($data);
+ $this->close();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function close(): void
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->buffer = [];
+ $this->closed = true;
+ $this->emit('close');
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/AbstractBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/AbstractBuffer.php
new file mode 100644
index 0000000..d4d677f
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/AbstractBuffer.php
@@ -0,0 +1,28 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+abstract class AbstractBuffer implements ReadableBuffer, WriteableBuffer
+{
+ abstract public function __construct($argument);
+
+ abstract public function __toString(): string;
+
+ abstract public function length(): int;
+
+ abstract public function getLastEmptyPosition(): int;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/Buffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/Buffer.php
new file mode 100644
index 0000000..58290ea
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/Buffer.php
@@ -0,0 +1,309 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers\ByteBuffer;
+
+use Discord\Voice\Helpers\FormatPackEnum;
+
+/**
+ * Helper class for handling binary data.
+ *
+ * @author alexandre433
+ *
+ * @throws \InvalidArgumentException If invalid arguments are provided or buffer overflows.
+ */
+class Buffer extends AbstractBuffer implements \ArrayAccess
+{
+ use BufferArrayAccessTrait;
+
+ protected \SplFixedArray $buffer;
+
+ public function __construct($argument)
+ {
+ is_string($argument)
+ ? $this->initializeStructs(strlen($argument), $argument)
+ : (is_int($argument)
+ ? $this->initializeStructs($argument, pack(FormatPackEnum::x->value."$argument"))
+ : throw new \InvalidArgumentException('Constructor argument must be an binary string or integer'));
+ }
+
+ public function __toString(): string
+ {
+ return implode('', iterator_to_array($this->buffer, false));
+ }
+
+ public static function make($argument): static
+ {
+ return new static($argument);
+ }
+
+ protected function initializeStructs($length, string $content): void
+ {
+ $this->buffer = new \SplFixedArray($length);
+ for ($i = 0; $i < $length; $i++) {
+ $this->buffer[$i] = $content[$i];
+ }
+ }
+
+ /**
+ * Inserts a value into the buffer at the specified offset.
+ *
+ * @param FormatPackEnum|string $format
+ * @param mixed $value
+ * @param int $offset
+ * @param ?int $length
+ * @return Buffer
+ */
+ protected function insert($format, $value, int $offset, ?int $length = null): self
+ {
+ $bytes = pack($format?->value ?? $format, $value);
+
+ if (null === $length) {
+ $length = strlen($bytes);
+ }
+
+ for ($i = 0; $i < strlen($bytes); $i++) {
+ $this->buffer[$offset++] = $bytes[$i];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Extracts a value from the buffer at the specified offset.
+ *
+ * @param FormatPackEnum|string $format
+ * @param int $offset
+ * @param int $length
+ * @return mixed
+ */
+ protected function extract(FormatPackEnum|string $format, int $offset, int $length)
+ {
+ $encoded = '';
+ for ($i = 0; $i < $length; $i++) {
+ $encoded .= $this->buffer->offsetGet($offset + $i);
+ }
+
+ if ($format == FormatPackEnum::N && PHP_INT_SIZE <= 4) {
+ [, $h, $l] = unpack('n*', $encoded);
+ $result = $l + $h * 0x010000;
+ } elseif ($format == FormatPackEnum::V && PHP_INT_SIZE <= 4) {
+ [, $h, $l] = unpack('v*', $encoded);
+ $result = $h + $l * 0x010000;
+ } else {
+ [, $result] = unpack($format?->value ?? $format, $encoded);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Checks if the actual value exceeds the expected maximum size.
+ *
+ * @param mixed $excpectedMax
+ * @param mixed $actual
+ * @throws \InvalidArgumentException
+ * @return static
+ */
+ protected function checkForOverSize($expectedMax, string|int $actual): self
+ {
+ if ($actual > $expectedMax) {
+ throw new \InvalidArgumentException('actual exceeded expectedMax limit');
+ }
+
+ return $this;
+ }
+
+ public function length(): int
+ {
+ return $this->buffer->getSize();
+ }
+
+ public function getLastEmptyPosition(): int
+ {
+ foreach ($this->buffer as $key => $value) {
+ if (empty(trim($value))) {
+ return $key;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Writes a string to the buffer at the specified offset.
+ *
+ * @param string $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function write($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $length = strlen($value);
+ $this->insert('a'.$length, $value, $offset, $length);
+
+ return $this;
+ }
+
+ /**
+ * Writes an 8-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt8($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::C;
+ $this->checkForOverSize(0xff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 16-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt16BE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::n;
+ $this->checkForOverSize(0xffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 16-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt16LE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::v;
+ $this->checkForOverSize(0xffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 32-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ public function writeInt32BE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::N;
+ $this->checkForOverSize(0xffffffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Writes a 32-bit signed integer to the buffer at the specified offset.
+ *
+ * @param int $value The value that will be written.
+ * @param int|null $offset The offset that the value will be written at.
+ * @return static
+ */
+ #[\Override]
+ public function writeInt32LE($value, ?int $offset = null): self
+ {
+ if (null === $offset) {
+ $offset = $this->getLastEmptyPosition();
+ }
+
+ $format = FormatPackEnum::V;
+ $this->checkForOverSize(0xffffffff, $value);
+ $this->insert($format, $value, $offset, $format->getLength());
+
+ return $this;
+ }
+
+ /**
+ * Reads a string from the buffer at the specified offset.
+ *
+ * @param int $offset The offset to read from.
+ * @param int $length The length of the string to read.
+ * @return string The data read.
+ */
+ public function read(int $offset, int $length)
+ {
+ return $this->extract('a'.$length, $offset, $length);
+ }
+
+ public function readInt8(int $offset)
+ {
+ $format = FormatPackEnum::C;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt16BE(int $offset)
+ {
+ $format = FormatPackEnum::n;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt16LE(int $offset)
+ {
+ $format = FormatPackEnum::v;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt32BE(int $offset)
+ {
+ $format = FormatPackEnum::N;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+
+ public function readInt32LE(int $offset)
+ {
+ $format = FormatPackEnum::V;
+
+ return $this->extract($format, $offset, $format->getLength());
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/BufferArrayAccessTrait.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/BufferArrayAccessTrait.php
new file mode 100644
index 0000000..5dceefe
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/BufferArrayAccessTrait.php
@@ -0,0 +1,218 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers\ByteBuffer;
+
+use Discord\Voice\Helpers\FormatPackEnum;
+
+/**
+ * @author Valithor Obsidion
+ */
+trait BufferArrayAccessTrait
+{
+ /**
+ * Writes a 32-bit unsigned integer with big endian.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt32BE(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::I, $value, $offset, 3);
+ }
+
+ /**
+ * Writes a 64-bit unsigned integer with little endian.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt64LE(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::P, $value, $offset, 8);
+ }
+
+ /**
+ * Writes a signed integer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeInt(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::N, $value, $offset, 4);
+ }
+
+ /**
+ * Writes a unsigned integer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeUInt(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::I, $value, $offset, 4);
+ }
+
+ /**
+ * Reads a signed integer.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readInt(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::N, $offset, 4);
+ }
+
+ /**
+ * Reads a signed integer.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readUInt(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::I, $offset, 4);
+ }
+
+ /**
+ * Writes an unsigned big endian short.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeShort(int $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::n, $value, $offset, 2);
+ }
+
+ /**
+ * Reads an unsigned big endian short.
+ *
+ * @param int $offset The offset to read from.
+ *
+ * @return int The data read.
+ */
+ public function readShort(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::n, $offset, 4);
+ }
+
+ /**
+ * Reads a unsigned integer with little endian.
+ *
+ * @param int $offset The offset that will be read.
+ *
+ * @return int The value that is at the specified offset.
+ */
+ public function readUIntLE(int $offset): int
+ {
+ return $this->extract(FormatPackEnum::I, $offset, 3);
+ }
+
+ public function readChar(int $offset): string
+ {
+ return $this->extract(FormatPackEnum::c, $offset, 1);
+ }
+
+ public function readUChar(int $offset): string
+ {
+ return $this->extract(FormatPackEnum::C, $offset, 1);
+ }
+
+ /**
+ * Writes a char.
+ *
+ * @param string $value The value that will be written.
+ * @param int $offset The offset that the value will be written.
+ */
+ public function writeChar(string $value, int $offset): self
+ {
+ return $this->insert(FormatPackEnum::c, $value, $offset, FormatPackEnum::c->getLength());
+ }
+
+ /**
+ * Writes raw binary to the buffer.
+ *
+ * @param int $value The value that will be written.
+ * @param int $offset The offset that the value will be written at.
+ */
+ public function writeRaw(int $value, int $offset): void
+ {
+ $this->buffer[$offset] = $value;
+ }
+
+ /**
+ * Writes a binary string to the buffer.
+ *
+ * @param string $value The value that will be written.
+ * @param int $offset The offset that the value will be written at.
+ */
+ public function writeRawString(string $value, int $offset): void
+ {
+ for ($i = 0; $i < strlen($value); ++$i) {
+ $this->buffer[$offset++] = $value[$i];
+ }
+ }
+
+ /**
+ * Gets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->buffer[$key] ?? null;
+ }
+
+ /**
+ * Checks if an attribute exists via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ *
+ * @return bool Whether the offset exists.
+ */
+ public function offsetExists($key): bool
+ {
+ return isset($this->buffer[$key]);
+ }
+
+ /**
+ * Sets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param mixed $key The attribute key.
+ * @param mixed $value The attribute value.
+ */
+ public function offsetSet($key, $value): void
+ {
+ $this->buffer[$key] = $value;
+ }
+
+ /**
+ * Unsets an attribute via key. Used for \ArrayAccess.
+ *
+ * @param string $key The attribute key.
+ */
+ public function offsetUnset($key): void
+ {
+ if (isset($this->buffer[$key])) {
+ unset($this->buffer[$key]);
+ }
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/ReadableBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/ReadableBuffer.php
new file mode 100644
index 0000000..d5e53c5
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/ReadableBuffer.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+interface ReadableBuffer
+{
+ public function read(int $offset, int $length);
+
+ public function readInt8(int $offset);
+
+ public function readInt16BE(int $offset);
+
+ public function readInt16LE(int $offset);
+
+ public function readInt32BE(int $offset);
+
+ public function readInt32LE(int $offset);
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/WriteableBuffer.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/WriteableBuffer.php
new file mode 100644
index 0000000..063c426
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/ByteBuffer/WriteableBuffer.php
@@ -0,0 +1,67 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers\ByteBuffer;
+
+/**
+ * @author alexandre433
+ */
+interface WriteableBuffer
+{
+ public function write($value, ?int $offset = null): self;
+
+ /**
+ * Write an int8 to the buffer.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt8($value, ?int $offset = null): self;
+
+ /**
+ * Write an int16 to the buffer in big-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt16BE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int16 to the buffer in little-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt16LE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int32 to the buffer in big-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt32BE($value, ?int $offset = null): self;
+
+ /**
+ * Write an int32 to the buffer in little-endian format.
+ *
+ * @param mixed $value
+ * @param int|null $offset The offset to write the int8, if not provided the length of the buffer will be used
+ * @return self
+ */
+ public function writeInt32LE($value, ?int $offset = null): self;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/FormatPackEnum.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/FormatPackEnum.php
new file mode 100644
index 0000000..519764d
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Helpers/FormatPackEnum.php
@@ -0,0 +1,178 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Helpers;
+
+/**
+ * @link https://www.php.net/manual/en/function.pack.php
+ */
+enum FormatPackEnum: string
+{
+ /**
+ * NUL-padded string.
+ */
+ case a = 'a';
+
+ /**
+ * SPACE-padded string.
+ */
+ case A = 'A';
+
+ /**
+ * Hex string, low nibble first.
+ */
+ case h = 'h';
+
+ /**
+ * Hex string, high nibble first.
+ */
+ case H = 'H';
+
+ /**
+ * signed char.
+ */
+ case c = 'c';
+
+ /**
+ * unsigned char.
+ */
+ case C = 'C';
+
+ /**
+ * signed short (always 16 bit, machine byte order).
+ */
+ case s = 's';
+
+ /**
+ * unsigned short (always 16 bit, machine byte order).
+ */
+ case S = 'S';
+
+ /**
+ * unsigned short (always 16 bit, big endian byte order).
+ */
+ case n = 'n';
+
+ /**
+ * unsigned short (always 16 bit, little endian byte order).
+ */
+ case v = 'v';
+
+ /**
+ * signed integer (machine dependent size and byte order).
+ */
+ case i = 'i';
+
+ /**
+ * unsigned integer (machine dependent size and byte order).
+ */
+ case I = 'I';
+
+ /**
+ * signed long (always 32 bit, machine byte order).
+ */
+ case l = 'l';
+
+ /**
+ * unsigned long (always 32 bit, machine byte order).
+ */
+ case L = 'L';
+
+ /**
+ * unsigned long (always 32 bit, big endian byte order).
+ */
+ case N = 'N';
+
+ /**
+ * unsigned long (always 32 bit, little endian byte order).
+ */
+ case V = 'V';
+
+ /**
+ * signed long long (always 64 bit, machine byte order).
+ */
+ case q = 'q';
+
+ /**
+ * unsigned long long (always 64 bit, machine byte order).
+ */
+ case Q = 'Q';
+
+ /**
+ * unsigned long long (always 64 bit, big endian byte order).
+ */
+ case J = 'J';
+
+ /**
+ * unsigned long long (always 64 bit, little endian byte order).
+ */
+ case P = 'P';
+
+ /**
+ * float (machine dependent size and representation).
+ */
+ case f = 'f';
+
+ /**
+ * float (machine dependent size, little endian byte order).
+ */
+ case g = 'g';
+
+ /**
+ * float (machine dependent size, big endian byte order).
+ */
+ case G = 'G';
+
+ /**
+ * double (machine dependent size and representation).
+ */
+ case d = 'd';
+
+ /**
+ * double (machine dependent size, little endian byte order).
+ */
+ case e = 'e';
+
+ /**
+ * double (machine dependent size, big endian byte order).
+ */
+ case E = 'E';
+
+ /**
+ * NUL byte.
+ */
+ case x = 'x';
+
+ /**
+ * Back up one byte.
+ */
+ case X = 'X';
+
+ /**
+ * NUL-padded string.
+ */
+ case Z = 'Z';
+
+ /**
+ * NUL-fill to absolute position.
+ */
+ case At = '@';
+
+ public function getLength(): int
+ {
+ return match ($this) {
+ self::n, self::v => 2,
+ self::N, self::V => 4,
+ self::c, self::C => 1,
+ default => throw new \InvalidArgumentException('Invalid format pack'),
+ };
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Manager.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Manager.php
new file mode 100644
index 0000000..b67e977
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Manager.php
@@ -0,0 +1,226 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+use Discord\Discord;
+use Discord\Voice\Exceptions\Channels\CantJoinMoreThanOneChannelException;
+use Discord\Voice\Exceptions\Channels\CantSpeakInChannelException;
+use Discord\Voice\Exceptions\Channels\ChannelMustAllowVoiceException;
+use Discord\Voice\Exceptions\Channels\EnterChannelDeniedException;
+use Discord\Parts\Channel\Channel;
+use Discord\Parts\WebSockets\VoiceServerUpdate;
+use Discord\Parts\WebSockets\VoiceStateUpdate;
+use Discord\WebSockets\Event;
+use Discord\WebSockets\Op;
+use Discord\WebSockets\VoicePayload;
+use Evenement\EventEmitterTrait;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+
+/**
+ * Manages many voice clients for the bot.
+ *
+ * @requires libopus - Linux | NOT TESTED - WINDOWS
+ * @requires FFMPEG - Linux | NOT TESTED - WINDOWS
+ *
+ * @since 10.19.0
+ */
+final class Manager
+{
+ use EventEmitterTrait;
+
+ /**
+ * @param Discord $discord
+ * @param array $clients
+ */
+ public function __construct(
+ protected Discord $discord,
+ public array $clients = [],
+ ) {
+ }
+
+ /**
+ * Handles the creation of a new voice client and joins the specified channel.
+ *
+ * @param \Discord\Parts\Channel\Channel $channel
+ * @param \Discord\Discord $discord
+ * @param array &$voice_sessions
+ * @param bool $mute
+ * @param bool $deaf
+ *
+ * @throws \Discord\Voice\Exceptions\Channels\ChannelMustAllowVoiceException
+ * @throws \Discord\Voice\Exceptions\Channels\EnterChannelDeniedException
+ * @throws \Discord\Voice\Exceptions\Channels\CantJoinMoreThanOneChannelException
+ * @throws \Discord\Voice\Exceptions\Channels\CantSpeakInChannelException
+ *
+ * @return \React\Promise\PromiseInterface
+ */
+ public function joinChannel(Channel $channel, Discord $discord, array &$voice_sessions, bool $mute = false, bool $deaf = true): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ try {
+ if (! $channel->isVoiceBased()) {
+ throw new ChannelMustAllowVoiceException();
+ }
+
+ $botperms = $channel->getBotPermissions();
+
+ if (! $botperms->connect) {
+ throw new EnterChannelDeniedException();
+ }
+
+ if (! $botperms->speak && ! $mute) {
+ throw new CantSpeakInChannelException();
+ }
+
+ if (isset($this->clients[$channel->guild_id])) {
+ throw new CantJoinMoreThanOneChannelException();
+ }
+ } catch (\Throwable $th) {
+ $deferred->reject($th);
+
+ return $deferred->promise();
+ }
+
+ // The same as new Client(...)
+ $this->clients[$channel->guild_id] = Client::make(
+ $this->discord,
+ $channel,
+ $voice_sessions,
+ ['dnsConfig' => $discord->options['dnsConfig']],
+ $deaf,
+ $mute,
+ $deferred,
+ $this,
+ false
+ );
+
+ $discord->on(Event::VOICE_STATE_UPDATE, fn ($state) => $this->stateUpdate($state, $channel));
+ // Creates Voice Client and waits for the voice server update.
+ $discord->on(Event::VOICE_SERVER_UPDATE, fn ($state, Discord $discord) => $this->serverUpdate($state, $channel, $discord, $deferred));
+
+ $discord->send(VoicePayload::new(
+ Op::OP_UPDATE_VOICE_STATE,
+ [
+ 'guild_id' => $channel->guild_id,
+ 'channel_id' => $channel->id,
+ 'self_mute' => $mute,
+ 'self_deaf' => $deaf,
+ ],
+ ));
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Retrieves the voice client for a given guild id.
+ *
+ * @param string|int $guildId
+ *
+ * @return \Discord\Voice\Client|null
+ */
+ public function getClient(string|int|Channel $guildChannelOrId): ?Client
+ {
+ if ($guildChannelOrId instanceof Channel) {
+ $guildChannelOrId = $guildChannelOrId->guild_id;
+ }
+
+ if (! isset($this->clients[$guildChannelOrId])) {
+ return null;
+ }
+
+ return $this->clients[$guildChannelOrId];
+ }
+
+ /**
+ * Handles the voice state update event to update session information for the voice client.
+ *
+ * @param \Discord\Parts\WebSockets\VoiceStateUpdate $state
+ * @param \Discord\Parts\Channel\Channel $channel
+ */
+ public function stateUpdate(VoiceStateUpdate $state, Channel $channel): void
+ {
+ if ($state->guild_id != $channel->guild_id) {
+ return; // This voice state update isn't for our guild.
+ }
+
+ $client = $this->getClient($channel);
+ if (! $client) {
+ return; // We might have left the voice channel already.
+ }
+
+ $client->setData([
+ 'session' => $state->session_id,
+ 'deaf' => $state->deaf,
+ 'mute' => $state->mute,
+ ]);
+
+ $this->discord->getLogger()->info('received session id for voice session', ['guild' => $channel->guild_id, 'session_id' => $state->session_id]);
+ $this->discord->voice_sessions[$channel->guild_id] = $state->session_id;
+ }
+
+ /**
+ * Handles the voice server update event to create a new voice client with the provided state.
+ *
+ * @param \Discord\Parts\WebSockets\VoiceServerUpdate $state
+ * @param \Discord\Parts\Channel\Channel $channel
+ * @param \Discord\Discord $discord
+ * @param \React\Promise\Deferred $deferred
+ */
+ protected function serverUpdate(VoiceServerUpdate $state, Channel $channel, Discord $discord, Deferred $deferred): void
+ {
+ if ($state->guild_id !== $channel->guild_id) {
+ return; // This voice server update isn't for our guild.
+ }
+
+ $client = $this->getClient($channel);
+ if (! $client) {
+ return; // We might have left the voice channel already.
+ }
+
+ $this->discord->getLogger()->info('received token and endpoint for voice session', [
+ 'guild' => $channel->guild_id,
+ 'token' => '*****',
+ 'endpoint' => $state->endpoint,
+ ]);
+
+ $client->once('ready', function () use (&$client, $deferred, $channel) {
+ $this->discord->logger->info('voice manager is ready');
+ $this->discord->voice->clients[$channel->guild_id] = $client;
+ $deferred->resolve($client);
+ });
+ $client->once('error', function ($e) use ($deferred) {
+ $this->discord->logger->error('error initializing voice manager', ['e' => $e->getMessage()]);
+ $deferred->reject($e);
+ });
+ $client->once('close', function () use ($channel) {
+ $this->discord->logger->warning('voice manager closed');
+ unset($this->discord->voice->clients[$channel->guild_id]);
+ unset($this->discord->voice_sessions[$channel->guild_id]);
+ });
+
+ $client->setData(
+ array_merge(
+ $client->data,
+ [
+ 'token' => $state->token,
+ 'endpoint' => $state->endpoint,
+ 'session' => $client->data['session'] ?? null,
+ ],
+ ['dnsConfig' => $discord->options['dnsConfig']]
+ )
+ );
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/OggPage.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/OggPage.php
new file mode 100644
index 0000000..ab9dc1a
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/OggPage.php
@@ -0,0 +1,144 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+use Discord\Voice\Helpers\Buffer;
+use Generator;
+use React\Promise\PromiseInterface;
+
+/**
+ * Represents a page in an Ogg container.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc3533
+ *
+ * @since 10.0.0
+ *
+ * @internal
+ */
+class OggPage
+{
+ /**
+ * Binary format string used to parse header.
+ *
+ * @var string
+ */
+ private const FORMAT = 'Cversion/Cheader_type/Pgranule_position/Vbitstream_sn/Vpage_seq/Vcsum/Cpage_segments';
+
+ /**
+ * Create a new Ogg page.
+ *
+ * @param int $version The version number of the Ogg file format used in this stream.
+ * @param int $headerType Identifies the specific type of this page.
+ * @param int $granulePosition Contains position information.
+ * @param int $bitstreamSn Contains the unique serial number by which the logical bitstream is identified.
+ * @param int $pageSeq Contains the sequence number of the page so the decoder can identify page loss.
+ * @param int $checksum Contains a 32 bit CRC checksum of the page (including header with zero CRC field and page content).
+ * @param int[] $pageSegments A list of page segment lengths which were contained within the page.
+ * @param string $segmentData The data of all the page segments concatenated.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc3533#section-6 The Ogg page format
+ */
+ private function __construct(
+ private int $version,
+ private int $headerType,
+ private int $granulePosition,
+ private int $bitstreamSn,
+ private int $pageSeq,
+ private int $checksum,
+ private array $pageSegments,
+ public string $segmentData,
+ ) {
+ }
+
+ /**
+ * Read an Ogg page from a buffer.
+ *
+ * @param Buffer $buffer Buffer to read the Ogg page from.
+ * @param ?int $timeout Time in milliseconds before a buffer read times out.
+ *
+ * @return PromiseInterface Promise containing the Ogg page.
+ *
+ * @throws \UnexpectedValueException If the buffer is out of sync and an invalid header is read.
+ */
+ public static function fromBuffer(Buffer $buffer, ?int $timeout = -1): PromiseInterface
+ {
+ $header = null;
+ $pageSegments = [];
+
+ return $buffer->read(4, timeout: $timeout)->then(function ($magic) use ($buffer, $timeout) {
+ if ($magic !== 'OggS') {
+ throw new \UnexpectedValueException("Invalid Ogg page header, expected OggS got {$magic}.");
+ }
+
+ return $buffer->read(23, timeout: $timeout);
+ })
+ // Reading header
+ ->then(function ($data) use ($buffer, &$header, $timeout) {
+ $header = unpack(self::FORMAT, $data);
+
+ return $buffer->read($header['page_segments'], timeout: $timeout);
+ })
+ // Reading page segment lengths
+ ->then(function ($data) use ($buffer, &$pageSegments, $timeout) {
+ $pageSegments = unpack('C*', $data);
+ $data = array_sum($pageSegments);
+
+ return $buffer->read($data, timeout: $timeout);
+ })
+ // Reading segment data
+ ->then(function ($data) use (&$header, &$pageSegments) {
+ return new OggPage(
+ $header['version'],
+ $header['header_type'],
+ $header['granule_position'],
+ $header['bitstream_sn'],
+ $header['page_seq'],
+ $header['csum'],
+ $pageSegments,
+ $data
+ );
+ });
+ }
+
+ /**
+ * Iterates through the packets contained within the stream, yielding an
+ * array containing binary data and whether the packet is complete, from a
+ * generator.
+ *
+ * @return Generator
+ */
+ public function iterPackets()
+ {
+ $packetLen = 0;
+ $offset = 0;
+ $partial = true;
+
+ foreach ($this->pageSegments as $seg) {
+ $packetLen += $seg;
+ if ($seg === 255) {
+ $partial = true;
+ continue;
+ }
+
+ yield [substr($this->segmentData, $offset, $packetLen), true];
+ $offset += $packetLen;
+ $packetLen = 0;
+ $partial = false;
+ }
+
+ if ($partial) {
+ yield [substr($this->segmentData, $offset), false];
+ }
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/OggStream.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/OggStream.php
new file mode 100644
index 0000000..e9e4009
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/OggStream.php
@@ -0,0 +1,145 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+use Discord\Voice\Exceptions\BufferTimedOutException;
+use Discord\Voice\Helpers\Buffer;
+use React\Promise\PromiseInterface;
+use React\Promise\Promise;
+
+use function React\Promise\resolve;
+
+/**
+ * Represents an Ogg Opus stream.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc7845
+ *
+ * @since 10.0.0
+ *
+ * @internal
+ */
+class OggStream
+{
+ /**
+ * Leftover bytes from the previous Ogg packet.
+ *
+ * @var string
+ */
+ private string $leftover = '';
+
+ /**
+ * Buffer of packets that have been parsed and split into Opus chunks.
+ *
+ * @var string[]|null
+ */
+ private ?array $packets = [];
+
+ /**
+ * Create a new Ogg Opus stream.
+ *
+ * @param Buffer $buffer Buffer to read Ogg Opus packets from.
+ * @param OpusHead $header The header that has already been read from `$buffer`.
+ * @param OpusTags $tags The tags that have already been read from `$buffer`.
+ */
+ private function __construct(
+ private Buffer $buffer,
+ public OpusHead $header,
+ public OpusTags $tags
+ ) {
+ }
+
+ /**
+ * Create a new Ogg Opus stream from a buffer. This will read the Opus
+ * header and the Opus tags and return a new Ogg stream ready to read Opus
+ * packets.
+ *
+ * @param Buffer $buffer Buffer to read Ogg Opus packets from.
+ * @param ?int $timeout Time in milliseconds before a buffer read times out.
+ *
+ * @return PromiseInterface A promise containing the Ogg stream.
+ */
+ public static function fromBuffer(Buffer $buffer, ?int $timeout = -1): PromiseInterface
+ {
+ /** @var OpusHead */
+ $header = null;
+
+ return OggPage::fromBuffer($buffer, $timeout)->then(function (OggPage $page) use (&$header, $buffer, $timeout) {
+ $header = new OpusHead($page->segmentData);
+
+ return OggPage::fromBuffer($buffer, $timeout);
+ })->then(function (OggPage $page) use (&$header, $buffer) {
+ $tags = new OpusTags($page->segmentData);
+
+ return new OggStream($buffer, $header, $tags);
+ });
+ }
+
+ /**
+ * Attempt to get a packet from the Ogg stream.
+ *
+ * @return PromiseInterface Promise containing an Opus packet. If null, indicates EOF.
+ */
+ public function getPacket(): PromiseInterface
+ {
+ if ($this->packets === null) {
+ return resolve(null);
+ } elseif (count($this->packets) > 0) {
+ return resolve(array_shift($this->packets));
+ }
+
+ return $this->parsePackets()->then(function ($packets) {
+ if ($packets === null) {
+ $this->packets = null;
+
+ return null;
+ }
+
+ $this->packets = array_merge($this->packets, $packets);
+
+ return $this->getPacket();
+ });
+ }
+
+ /**
+ * Attempt to read an Ogg page from the buffer and parse it into Opus
+ * packets.
+ *
+ * @return PromiseInterface Promise containing an array of Opus packets.
+ */
+ private function parsePackets(): PromiseInterface
+ {
+ return new Promise(function ($resolve, $reject) {
+ OggPage::fromBuffer($this->buffer, timeout: 0)->then(function ($page) use ($resolve) {
+ $packets = [];
+ $partial = $this->leftover;
+ foreach ($page->iterPackets() as [$data, $complete]) {
+ $partial .= $data;
+ if ($complete) {
+ $packets[] = $partial;
+ $partial = '';
+ }
+ }
+ $this->leftover = $partial;
+
+ $resolve($packets);
+ }, function (\Exception $e) use ($resolve, $reject) {
+ if ($e instanceof BufferTimedOutException) {
+ $resolve(null);
+ } else {
+ $reject($e);
+ }
+ });
+ });
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusHead.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusHead.php
new file mode 100644
index 0000000..2fb1cb9
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusHead.php
@@ -0,0 +1,137 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+/**
+ * Represents the header attached to an Opus Ogg file.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc7845#section-5.1 Identification Header
+ *
+ * @since 10.0.0
+ *
+ * @internal
+ */
+class OpusHead
+{
+ /**
+ * Binary format string used to parse header.
+ *
+ * @var string
+ */
+ private const FORMAT = 'Cversion/Cchannel_count/vpre_skip/Vsample_rate/voutput_gain/Cchannel_map_family';
+
+ /**
+ * Binary format string used to parse optional header.
+ *
+ * @var string
+ */
+ private const STREAM_COUNT_FORMAT = 'Cstream_count/Ctwo_channel_stream_count';
+
+ /**
+ * Version number of Opus file. Should always be 1.
+ *
+ * @var int
+ */
+ public int $version;
+
+ /**
+ * Number of channels in the Opus file.
+ *
+ * @var int
+ */
+ public int $channelCount;
+
+ /**
+ * The number of samples (at 48 kHz) to discard from the decoder output when
+ * starting playback.
+ *
+ * @var int
+ */
+ public int $preSkip;
+
+ /**
+ * The sample rate of the original input (before encoding), in Hz. This
+ * field is _not_ the sample rate to use for playback of the encoded data.
+ *
+ * @var int
+ */
+ public int $sampleRate;
+
+ /**
+ * The gain to be applied when decoding.
+ *
+ * @var int
+ */
+ public int $outputGain;
+
+ /**
+ * Indicates the order and semantic meaning of the output channels.
+ *
+ * @var int
+ */
+ public int $channelMapFamily;
+
+ /**
+ * The total number of streams encoded in each Ogg packet.
+ *
+ * @var int|null
+ */
+ public ?int $streamCount = null;
+
+ /**
+ * The number of streams whose decoders are to be configured to produce two
+ * channels (stereo).
+ *
+ * @var int|null
+ */
+ public ?int $twoChannelStreamCount = null;
+
+ /**
+ * Contains one octet per output channel, indicating which decoded channel
+ * is to be used for each one.
+ *
+ * @var int[]|null
+ */
+ public ?array $cmap = null;
+
+ /**
+ * Create an instance of OpusHead from a binary string.
+ *
+ * @param string $data Binary string of data.
+ *
+ * @throws \UnexpectedValueException If the binary data was missing the magic bytes.
+ */
+ public function __construct(string $data)
+ {
+ $magic = substr($data, 0, 8);
+ if ($magic != 'OpusHead') {
+ throw new \UnexpectedValueException("Expected OpusHead, found {$magic}.");
+ }
+
+ $head = unpack(self::FORMAT, $data, 8);
+ $this->version = $head['version'];
+ $this->channelCount = $head['channel_count'];
+ $this->preSkip = $head['pre_skip'];
+ $this->sampleRate = $head['sample_rate'];
+ $this->outputGain = $head['output_gain'];
+ $this->channelMapFamily = $head['channel_map_family'];
+
+ if ($head['channel_map_family'] > 0) {
+ $stream_counts = unpack(self::STREAM_COUNT_FORMAT, $data, 19);
+ $this->streamCount = $stream_counts['stream_count'];
+ $this->twoChannelStreamCount = $stream_counts['two_channel_stream_count'];
+ $this->cmap = array_values(unpack('C*', $data, 21));
+ }
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusTags.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusTags.php
new file mode 100644
index 0000000..9b6cfed
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/OpusTags.php
@@ -0,0 +1,68 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+/**
+ * Represents Vorbis tags attached to an Opus Ogg file.
+ *
+ * @link https://www.rfc-editor.org/rfc/rfc7845#section-5.2 Comment Header
+ *
+ * @since 10.0.0
+ *
+ * @internal
+ */
+class OpusTags
+{
+ /**
+ * The vendor of the Opus Ogg.
+ *
+ * @var string
+ */
+ public string $vendor;
+
+ /**
+ * An array of tags attached to the Opus Ogg.
+ *
+ * @var string[]
+ */
+ public array $tags;
+
+ /**
+ * Create an instance of OpusTags from a binary string.
+ *
+ * @param string $data Binary string of data.
+ *
+ * @throws \UnexpectedValueException If the binary data was missing the magic bytes.
+ */
+ public function __construct(string $data)
+ {
+ $magic = substr($data, 0, 8);
+ if ($magic != 'OpusTags') {
+ throw new \UnexpectedValueException("Expected OpusTags, found {$magic}.");
+ }
+
+ $vendor_len = unpack('Vvendor_len', $data, 8)['vendor_len'];
+ $this->vendor = substr($data, 12, $vendor_len);
+
+ $tags = [];
+ $num_tags = unpack('Vnum_tags', $data, 12 + $vendor_len)['num_tags'];
+ $data = substr($data, 16 + $vendor_len);
+ for ($i = 0; $i < $num_tags; $i++) {
+ $tag_len = unpack('Vtag_len', $data)['tag_len'];
+ $tags[$i] = substr($data, 4, $tag_len);
+ $data = substr($data, 4 + $tag_len);
+ }
+ $this->tags = $tags;
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/DCA.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/DCA.php
new file mode 100644
index 0000000..339d210
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/DCA.php
@@ -0,0 +1,100 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Processes;
+
+use React\ChildProcess\Process;
+
+/**
+ * Handles the encoding and decoding of audio streams using DCA format.
+ *
+ * @since 10.19.0
+ */
+final class DCA extends ProcessAbstract
+{
+ /**
+ * The DCA version the client is using.
+ *
+ * @var string The DCA version.
+ */
+ public const DCA_VERSION = 'DCA1';
+
+ protected static string $exec = 'dca';
+
+ public static function checkForDca(): bool
+ {
+ $binaries = [
+ 'dca',
+ ];
+
+ foreach ($binaries as $binary) {
+ $output = self::checkForExecutable($binary);
+
+ if (null !== $output) {
+ self::$exec = $output;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Encodes audio to DCA format.
+ *
+ * @param string|null $filename The input filename, or null for pipe input.
+ * @param int|float $volume The volume adjustment in dB.
+ * @param int $bitrate The bitrate for the output audio.
+ * @param array|null $preArgs Additional arguments to pass before the main flags.
+ *
+ * @TODO Implement function, was not in original code.
+ *
+ * @return Process
+ */
+ public static function encode(
+ ?string $filename = null,
+ int|float $volume = 0,
+ int $bitrate = 128000,
+ ?array $preArgs = null
+ ): Process {
+ $flags = [
+ '-ab', round($bitrate / 1000), // Bitrate
+ '-mode', 'decode', // Decode mode
+ ];
+
+ $flags = implode(' ', $flags);
+
+ return new Process(self::$exec." $flags");
+ }
+
+ public static function decode(
+ ?string $filename = null,
+ int|float $volume = 0,
+ int $bitrate = 128000,
+ int $channels = 2,
+ ?int $frameSize = null,
+ ?array $preArgs = null,
+ ): Process {
+ if (null === $frameSize) {
+ $frameSize = round($frameSize * 48);
+ }
+
+ $flags = [
+ ];
+
+ $flags = implode(' ', $flags);
+
+ return new Process(self::$exec." $flags");
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/Ffmpeg.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/Ffmpeg.php
new file mode 100644
index 0000000..b3e78ee
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/Ffmpeg.php
@@ -0,0 +1,164 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Processes;
+
+use Discord\Voice\Exceptions\Libraries\FFmpegNotFoundException;
+use React\ChildProcess\Process;
+
+/**
+ * Handles the decoding and encoding of audio streams using FFmpeg.
+ *
+ * @since 10.19.0
+ */
+final class Ffmpeg extends ProcessAbstract
+{
+ protected static string $exec = '/usr/bin/ffmpeg';
+
+ public function __construct()
+ {
+ if (! $this->checkForFFmpeg()) {
+ throw new FFmpegNotFoundException('FFmpeg binary not found.');
+ }
+ }
+
+ public static function __callStatic(string $name, array $arguments)
+ {
+ if (method_exists(self::class, $name) && in_array($name, ['encode', 'decode'])) {
+ if (! self::checkForFFmpeg()) {
+ throw new FFmpegNotFoundException('FFmpeg binary not found.');
+ }
+
+ return self::$name(...$arguments);
+ }
+
+ throw new \BadMethodCallException("Method {$name} does not exist in ".__CLASS__);
+ }
+
+ public static function checkForFFmpeg(): bool
+ {
+ $binaries = [
+ 'ffmpeg',
+ ];
+
+ foreach ($binaries as $binary) {
+ $output = self::checkForExecutable($binary);
+
+ if (null !== $output) {
+ self::$exec = $output;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static function encode(
+ ?string $filename = null,
+ int|float $volume = 0,
+ int $bitrate = 128000,
+ ?array $preArgs = null
+ ): Process {
+ $flags = [
+ '-i', $filename ?? 'pipe:0',
+ '-map_metadata', '-1',
+ '-f', 'opus',
+ '-c:a', 'libopus',
+ '-ar', parent::DEFAULT_KHZ,
+ '-af', "volume={$volume}dB",
+ '-ac', '2',
+ '-b:a', $bitrate,
+ '-loglevel', 'warning',
+ 'pipe:1',
+ ];
+
+ if (null !== $preArgs) {
+ $flags = array_merge($preArgs, $flags);
+ }
+
+ $flags = implode(' ', $flags);
+ $cmd = self::$exec." {$flags}";
+
+ return new Process(
+ $cmd,
+ fds: [
+ ['socket'],
+ ['socket'],
+ ['socket'],
+ ]
+ );
+ }
+
+ /**
+ * Decodes an Opus audio stream to OGG format using FFmpeg.
+ *
+ * TODO: Add support for Windows, currently only tested and ran on WSL2
+ *
+ * @param mixed $filename If there's no name, it will output to stdout
+ * (pipe:1). If a name is given, it will save the file
+ * with the given name. If the name does not end with
+ * .ogg, it will append .ogg to the name.
+ * If null, it will use 'pipe:1' as the filename.
+ * @param int|float $volume Default: 0
+ * @param int $bitrate Default: 128000
+ * @param int $channels Default: 2
+ * @param null|int $frameSize
+ * @param null|array $preArgs
+ * @return Process
+ */
+ public static function decode(
+ ?string $filename = null,
+ int|float $volume = 0,
+ int $bitrate = 128000,
+ int $channels = 2,
+ ?int $frameSize = null,
+ ?array $preArgs = null,
+ ): Process {
+ if (null === $frameSize) {
+ $frameSize = round(20 * 48);
+ }
+
+ if ($filename) {
+ $filename = date('Y-m-d_H-i').'-'.$filename;
+ if (! str_ends_with($filename, '.ogg')) {
+ $filename .= '.ogg';
+ }
+ } elseif (null === $filename) {
+ $filename = 'pipe:1';
+ }
+
+ $flags = [
+ '-loglevel', 'error', // Set log level to warning to reduce output noise
+ '-channel_layout', 'stereo',
+ '-ac', $channels,
+ '-ar', parent::DEFAULT_KHZ,
+ '-f', 's16le',
+ '-i', 'pipe:0',
+ '-acodec', 'libopus',
+ '-f', 'ogg',
+ '-ar', parent::DEFAULT_KHZ,
+ '-ac', $channels,
+ '-b:a', $bitrate,
+ $filename,
+ ];
+
+ if (null !== $preArgs) {
+ $flags = array_merge($preArgs, $flags);
+ }
+
+ $flags = implode(' ', $flags);
+
+ return new Process(self::$exec." {$flags}", fds: [['socket'], ['socket'], ['socket']]);
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusDecoderInterface.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusDecoderInterface.php
new file mode 100644
index 0000000..590cea2
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusDecoderInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Processes;
+
+/**
+ * Interface for Opus Decoder implementations.
+ */
+interface OpusDecoderInterface
+{
+ public function decode($data, int $channels = 2, int $audioRate = 48000): string;
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusFfi.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusFfi.php
new file mode 100644
index 0000000..266caaf
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/OpusFfi.php
@@ -0,0 +1,112 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Processes;
+
+use FFI;
+
+/**
+ * Handles the decoding of Opus audio data using FFI (Foreign Function Interface).
+ *
+ * @todo
+ *
+ * @property FFI $ffi
+ * @method int opus_packet_get_nb_frames(mixed $packet, int $len)
+ * @method int opus_packet_get_samples_per_frame(mixed $data, int $Fs)
+ * @method mixed opus_decoder_create(int $Fs, int $channels, mixed $error)
+ * @method int opus_decode(mixed $st, mixed $data, int $len, mixed $pcm, int $frame_size, int $decode_fec)
+ * @method void opus_decoder_destroy(mixed $st)
+ */
+class OpusFfi implements OpusDecoderInterface
+{
+ protected FFI $ffi;
+
+ public function __construct()
+ {
+ // Load libopus and define needed functions/types
+ $this->ffi = FFI::cdef('
+ typedef struct OpusDecoder OpusDecoder;
+ typedef short opus_int16;
+ typedef int opus_int32;
+
+ int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len);
+ int opus_packet_get_samples_per_frame(const unsigned char * data, opus_int32 Fs);
+
+ OpusDecoder *opus_decoder_create(opus_int32 Fs, int channels, int *error);
+ int opus_decode(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_int16 *pcm, int frame_size, int decode_fec);
+ void opus_decoder_destroy(OpusDecoder *st);
+ ', 'libopus.so.0');
+ }
+
+ public static function new(): self
+ {
+ return new self();
+ }
+
+ /**
+ * Creates a FFI instance (code in C) to decode Opus audio data.
+ * By using the libopus library, this function decodes Opus-encoded audio data
+ * into PCM samples.
+ *
+ * @param string|mixed $data The Opus-encoded audio data to decode.
+ *
+ * @return string The decoded PCM audio data as a string/binary.
+ */
+ public function decode($data, int $channels = 2, int $audioRate = 48000): string
+ {
+ $dataLength = strlen($data);
+ if ($dataLength < 0) {
+ return '';
+ }
+
+ $dataBuffer = $this->ffi->new("const unsigned char[$dataLength]", false);
+ FFI::memcpy($dataBuffer, $data, $dataLength);
+
+ $frames = $this->opus_packet_get_nb_frames($dataBuffer, $dataLength);
+ $samplesPerFrame = $this->opus_packet_get_samples_per_frame($dataBuffer, $audioRate);
+ $frameSize = $frames * $samplesPerFrame;
+
+ // Create decoder
+ $error = $this->ffi->new('int');
+ $decoder = $this->opus_decoder_create($audioRate, $channels, FFI::addr($error));
+
+ // Prepare output buffer for PCM samples
+ $pcm = $this->ffi->new('opus_int16['.$frameSize * $channels * 2 .']', false);
+
+ // Decode
+ $ret = $this->opus_decode($decoder, $dataBuffer, $dataLength, $pcm, $frameSize, 0);
+
+ // Clean up
+ $this->opus_decoder_destroy($decoder);
+
+ if ($ret < 0) {
+ /** @todo Handle decoding error */
+ return '';
+ }
+
+ // 2 bytes per sample
+ return FFI::string($pcm, $ret * $channels * 2);
+ }
+
+ /**
+ * Magic method to redirect method calls to the FFI instance.
+ *
+ * @param string $name
+ * @param array $arguments
+ * @return mixed
+ */
+ public function __call(string $name, array $arguments)
+ {
+ return $this->ffi->$name(...$arguments);
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/ProcessAbstract.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/ProcessAbstract.php
new file mode 100644
index 0000000..2ecef7d
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/Processes/ProcessAbstract.php
@@ -0,0 +1,66 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice\Processes;
+
+use React\ChildProcess\Process;
+
+/**
+ * Abstract class for handling audio processing in Discord voice.
+ *
+ * This class provides methods to encode and decode audio streams using different processes.
+ *
+ * @since 10.19.0
+ */
+abstract class ProcessAbstract
+{
+ /**
+ * Encodes audio to a specific format.
+ */
+ abstract public static function encode(?string $filename = null, int|float $volume = 0, int $bitrate = 128000, ?array $preArgs = null): Process;
+
+ /**
+ * Default KHz value for voice to be decoded after someone talks.
+ *
+ * @var int
+ */
+ public const DEFAULT_KHZ = 48000;
+
+ /**
+ * Decodes audio from a specific format.
+ */
+ abstract public static function decode(
+ ?string $filename = null,
+ int|float $volume = 0,
+ int $bitrate = 128000,
+ int $channels = 2,
+ ?int $frameSize = null,
+ ?array $preArgs = null,
+ ): Process;
+
+ /**
+ * Checks if the specified executable is available on the system.
+ */
+ public static function checkForExecutable(string $exec): ?string
+ {
+ $systemOs = substr(PHP_OS, 0, 3);
+ $which = 'command -v';
+ if (strtoupper($systemOs) === 'WIN') {
+ $which = 'where';
+ }
+ $shellExecutable = shell_exec("$which $exec");
+ $executable = rtrim((string) explode(PHP_EOL, $shellExecutable)[0]);
+
+ return is_executable($executable) ? $executable : null;
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/ReceiveStream.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/ReceiveStream.php
new file mode 100644
index 0000000..128f0ad
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/ReceiveStream.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+/**
+ * Handles recieving audio from Discord.
+ *
+ * @since 10.19.0 The class was renamed to ReceiveStream.
+ */
+class ReceiveStream extends RecieveStream
+{
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/RecieveStream.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/RecieveStream.php
new file mode 100644
index 0000000..b002f9e
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/RecieveStream.php
@@ -0,0 +1,287 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+use Evenement\EventEmitter;
+use React\Stream\DuplexStreamInterface;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * Handles recieving audio from Discord.
+ *
+ * @deprecated The class was renamed, kept for backwards compatibility.
+ * @since 3.2.0
+ */
+class RecieveStream extends EventEmitter implements DuplexStreamInterface
+{
+ /**
+ * Contains PCM data.
+ *
+ * @var string PCM data.
+ */
+ protected $pcmData = '';
+
+ /**
+ * Contains Opus data.
+ *
+ * @var string Opus data.
+ */
+ protected $opusData = '';
+
+ /**
+ * Is the stream paused?
+ *
+ * @var bool Whether the stream is paused.
+ */
+ protected $isPaused;
+
+ /**
+ * Whether the stream is closed.
+ *
+ * @var bool Whether the stream is closed.
+ */
+ protected $isClosed = false;
+
+ /**
+ * The PCM pause buffer.
+ *
+ * @var array The PCM pause buffer.
+ */
+ protected $pcmPauseBuffer = [];
+
+ /**
+ * The pause buffer.
+ *
+ * @var array The pause buffer.
+ */
+ protected $opusPauseBuffer = [];
+
+ /**
+ * Constructs a stream.
+ */
+ public function __construct()
+ {
+ // empty for now
+ }
+
+ /**
+ * Writes PCM audio data.
+ *
+ * @param string $pcm PCM audio data.
+ */
+ public function writePCM(string $pcm): void
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ if ($this->isPaused) {
+ $this->pcmPauseBuffer[] = $pcm;
+
+ return;
+ }
+
+ $this->pcmData .= $pcm;
+
+ $this->emit('pcm', [$pcm]);
+ }
+
+ /**
+ * Writes Opus audio data.
+ *
+ * @param string $opus Opus audio data.
+ */
+ public function writeOpus(string $opus): void
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ if ($this->isPaused) {
+ $this->opusPauseBuffer[] = $opus;
+
+ return;
+ }
+
+ $this->opusData .= $opus;
+
+ $this->emit('opus', [$opus]);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isReadable()
+ {
+ return ! $this->isPaused && ! $this->isClosed;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isWritable()
+ {
+ return $this->isReadable();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function write($data)
+ {
+ $this->writePCM($data);
+ $this->writeOpus($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function end($data = null)
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ $this->write($data);
+ $this->close();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function close()
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ $this->pause();
+ $this->emit('end', []);
+ $this->emit('close', []);
+ $this->isClosed = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function pause()
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ if ($this->isPaused) {
+ return;
+ }
+
+ $this->isPaused = true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function resume()
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ if (! $this->isPaused) {
+ return;
+ }
+
+ $this->isPaused = false;
+
+ foreach ($this->pcmPauseBuffer as $data) {
+ $this->writePCM($data);
+ }
+
+ foreach ($this->opusPauseBuffer as $data) {
+ $this->writeOpus($data);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function pipe(WritableStreamInterface $dest, array $options = [])
+ {
+ $this->pipePCM($dest, $options);
+ $this->pipeOpus($dest, $options);
+ }
+
+ /**
+ * Pipes PCM to a destination stream.
+ *
+ * @param WritableStreamInterface $dest The stream to pipe to.
+ * @param array $options An array of options.
+ */
+ public function pipePCM(WritableStreamInterface $dest, array $options = []): void
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ $this->on('pcm', function ($data) use ($dest) {
+ $feedmore = $dest->write($data);
+
+ if (false === $feedmore) {
+ $this->pause();
+ }
+ });
+
+ $dest->on('drain', function () {
+ $this->resume();
+ });
+
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end && $this !== $dest) {
+ $this->on('end', function () use ($dest) {
+ $dest->end();
+ });
+ }
+ }
+
+ /**
+ * Pipes Opus to a destination stream.
+ *
+ * @param WritableStreamInterface $dest The stream to pipe to.
+ * @param array $options An array of options.
+ */
+ public function pipeOpus(WritableStreamInterface $dest, array $options = []): void
+ {
+ if ($this->isClosed) {
+ return;
+ }
+
+ $this->on('opus', function ($data) use ($dest) {
+ $feedmore = $dest->write($data);
+
+ if (false === $feedmore) {
+ $this->pause();
+ }
+ });
+
+ $dest->on('drain', function () {
+ $this->resume();
+ });
+
+ $end = isset($options['end']) ? $options['end'] : true;
+ if ($end && $this !== $dest) {
+ $this->on('end', function () use ($dest) {
+ $dest->end();
+ });
+ }
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php b/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php
new file mode 100644
index 0000000..adb1fae
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/Voice/VoiceClient.php
@@ -0,0 +1,1440 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\Voice;
+
+use Discord\Discord;
+use Discord\Exceptions\FileNotFoundException;
+use Discord\Voice\Exceptions\Channels\AudioAlreadyPlayingException;
+use Discord\Voice\Exceptions\ClientNotReadyException;
+use Discord\Voice\Exceptions\Libraries\OutdatedDCAException;
+use Discord\Voice\Helpers\Buffer as RealBuffer;
+use Discord\Helpers\Collection;
+use Discord\Helpers\ExCollectionInterface;
+use Discord\Parts\Channel\Channel;
+use Discord\Parts\WebSockets\VoiceStateUpdate;
+use Discord\Voice\Client\Packet;
+use Discord\Voice\Client\UDP;
+use Discord\Voice\Client\User;
+use Discord\Voice\Client\WS;
+use Discord\Voice\Processes\Dca;
+use Discord\Voice\Processes\Ffmpeg;
+use Discord\Voice\Processes\OpusDecoderInterface;
+use Discord\Voice\Processes\OpusFfi;
+use Discord\WebSockets\Op;
+use Discord\WebSockets\Payload;
+use Discord\WebSockets\VoicePayload;
+use Evenement\EventEmitter;
+use Ratchet\Client\WebSocket;
+use React\ChildProcess\Process;
+use React\Datagram\Socket;
+use React\Dns\Config\Config;
+use React\EventLoop\TimerInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use React\Stream\ReadableResourceStream as Stream;
+use React\Stream\ReadableStreamInterface;
+
+/**
+ * The Discord voice client.
+ *
+ * @since 10.19.0
+ */
+class VoiceClient extends EventEmitter
+{
+ /** Not speaking. */
+ public const NOT_SPEAKING = 0;
+ /** Normal transmission of voice audio. */
+ public const MICROPHONE = 1 << 0;
+ /** Transmission of context audio for video, no speaking indicator. */
+ public const SOUNDSHARE = 1 << 1;
+ /** Priority speaker, lowering audio of other speakers. */
+ public const PRIORITY_SPEAKER = 1 << 2;
+
+ /**
+ * Is the voice client ready?
+ *
+ * @var bool Whether the voice client is ready.
+ */
+ public bool $ready = false;
+
+ /**
+ * The voice WebSocket instance.
+ *
+ * @var WebSocket|null The voice WebSocket client.
+ */
+ public ?WebSocket $ws;
+
+ /**
+ * The UDP client instance.
+ *
+ * @var null|Socket|\Discord\Voice\Client\UDP
+ */
+ public ?UDP $udp;
+
+ /**
+ * The Opus Decoder instance.
+ *
+ * @var OpusDecoderInterface|null The Opus Decoder instance used for decoding audio.
+ */
+ public ?OpusDecoderInterface $opusdecoder = null;
+
+ /**
+ * The Voice WebSocket endpoint.
+ *
+ * @var string|null The endpoint the Voice WebSocket and UDP client will connect to.
+ */
+ public ?string $endpoint;
+
+ /**
+ * The UDP heartbeat interval.
+ *
+ * @var int|null How often we send a heartbeat packet.
+ */
+ public ?int $heartbeatInterval = null;
+
+ /**
+ * The Voice WebSocket heartbeat timer.
+ *
+ * @var TimerInterface|null The heartbeat periodic timer.
+ */
+ public ?TimerInterface $heartbeat = null;
+
+ /**
+ * The SSRC value.
+ *
+ * @var int|null The SSRC value used for RTP.
+ */
+ public ?int $ssrc;
+
+ /**
+ * The sequence of audio packets being sent.
+ *
+ * @var int The sequence of audio packets.
+ */
+ public ?int $seq = 0;
+
+ /**
+ * The timestamp of the last packet.
+ *
+ * @var int The timestamp the last packet was constructed.
+ */
+ public ?int $timestamp = 0;
+
+ /**
+ * @var int
+ */
+ public int $speaking = self::NOT_SPEAKING;
+
+ /**
+ * Whether the voice client is currently paused.
+ *
+ * @var bool
+ */
+ public bool $paused = false;
+
+ /**
+ * Have we sent the login frame yet?
+ *
+ * @var bool Whether we have sent the login frame.
+ */
+ public bool $sentLoginFrame = false;
+
+ /**
+ * The time we started sending packets.
+ *
+ * @var float|int|null The time we started sending packets.
+ */
+ public null|float|int $startTime;
+
+ /**
+ * The size of audio frames, in milliseconds.
+ *
+ * @var int The size of audio frames.
+ */
+ public int $frameSize = 20;
+
+ /**
+ * Collection of the status of people speaking.
+ *
+ * @var ExCollectionInterface Status of people speaking.
+ */
+ public $speakingStatus;
+
+ /**
+ * Collection of voice decoders.
+ *
+ * @var ExCollectionInterface Voice decoders.
+ */
+ public $voiceDecoders;
+
+ /**
+ * Voice audio recieve streams.
+ *
+ * @deprecated 10.5.0 Use receiveStreams instead.
+ *
+ * @var array|null Voice audio recieve streams.
+ */
+ public ?array $recieveStreams;
+
+ /**
+ * Voice audio receive streams.
+ *
+ * @var array|null Voice audio recieve streams.
+ */
+ public ?array $receiveStreams;
+
+ /**
+ * The volume the audio will be encoded with.
+ *
+ * @var int The volume that the audio will be encoded in.
+ */
+ protected int $volume = 100;
+
+ /**
+ * The audio application to encode with.
+ *
+ * Available: voip, audio (default), lowdelay
+ *
+ * @var string The audio application.
+ */
+ protected string $audioApplication = 'audio';
+
+ /**
+ * The bitrate to encode with.
+ *
+ * @var int Encoding bitrate.
+ */
+ protected int $bitrate = 128000;
+
+ /**
+ * Is the voice client reconnecting?
+ *
+ * @var bool Whether the voice client is reconnecting.
+ */
+ public bool $reconnecting = false;
+
+ /**
+ * Is the voice client being closed by user?
+ *
+ * @var bool Whether the voice client is being closed by user.
+ */
+ public bool $userClose = false;
+
+ /**
+ * The Config for DNS Resolver.
+ *
+ * @var Config|string|null
+ */
+ public null|string|Config $dnsConfig;
+
+ /**
+ * readopus Timer.
+ *
+ * @var TimerInterface|null Timer
+ */
+ public ?TimerInterface $readOpusTimer;
+
+ /**
+ * Audio Buffer.
+ *
+ * @var RealBuffer|null The Audio Buffer
+ */
+ public null|RealBuffer $buffer;
+
+ /**
+ * Current clients connected to the voice chat.
+ *
+ * @var array
+ */
+ public array $clientsConnected = [];
+
+ /**
+ * @var TimerInterface
+ */
+ public $monitorProcessTimer;
+
+ /**
+ * Users in the current voice channel.
+ *
+ * @var array Users in the current voice channel.
+ */
+ public array $users;
+
+ /**
+ * Time in which the streaming started.
+ *
+ * @var int
+ */
+ public int $streamTime = 0;
+
+ /**
+ * Silence Frame Remain Count.
+ *
+ * @var int Amount of silence frames remaining.
+ */
+ protected $silenceRemaining = 5;
+
+ /**
+ * Whether the current voice client is enabled to record audio.
+ *
+ * @var bool
+ */
+ protected bool $shouldRecord = false;
+
+ /**
+ * Constructs the Voice client instance.
+ *
+ * @param Discord $discord The Discord instance.
+ * @param Channel $channel
+ * @param string[] &$voice_sessions
+ * @param array $data
+ * @param bool $deaf Default: false
+ * @param bool $mute Default: false
+ * @param Deferred|null $deferred
+ * @param Manager|null $manager
+ */
+ public function __construct(
+ public Discord $discord,
+ public Channel $channel,
+ public array &$voice_sessions,
+ public array $data = [],
+ public bool $deaf = false,
+ public bool $mute = false,
+ protected ?Deferred $deferred = null,
+ public ?Manager &$manager = null,
+ protected bool $shouldBoot = true
+ ) {
+ $this->deaf = $this->data['deaf'] ?? false;
+ $this->mute = $this->data['mute'] ?? false;
+
+ $this->data['user_id'] = $this->discord->id;
+ $this->data['deaf'] = $this->deaf;
+ $this->data['mute'] = $this->mute;
+ $this->data['session'] = $this->data['session'] ?? null;
+
+ $this->speakingStatus = Collection::for(Speaking::class, 'ssrc');
+
+ if (extension_loaded('ffi')) {
+ $this->setDecoder(OpusFfi::new());
+ }
+
+ if ($this->shouldBoot) {
+ $this->boot();
+ }
+ }
+
+ /**
+ * Starts the voice client.
+ *
+ * @return bool
+ */
+ public function start(): bool
+ {
+ if (! Ffmpeg::checkForFFmpeg()) {
+ return false;
+ }
+
+ WS::make($this, $this->discord, $this->data);
+
+ return true;
+ }
+
+ /**
+ * Checks if an executable exists on the system.
+ *
+ * @param string $executable
+ * @return string|null
+ */
+ public static function checkForExecutable(string $executable): ?string
+ {
+ $systemOs = substr(PHP_OS, 0, 3);
+ $which = 'command -v';
+ if (strtoupper($systemOs) === 'WIN') {
+ $which = 'where';
+ }
+
+ $shellExecutable = shell_exec("$which $executable");
+ if ($shellExecutable === false) {
+ // Unable to establish pipe
+ return null;
+ }
+ if ($shellExecutable === null) {
+ // Error or the command produced no output
+ return null;
+ }
+ $executable = rtrim((string) explode(PHP_EOL, $shellExecutable)[0]);
+
+ return is_executable($executable) ? $executable : null;
+ }
+
+ /**
+ * Plays a file/url on the voice stream.
+ *
+ * @param string $file The file/url to play.
+ * @param int $channels Deprecated, Discord only supports 2 channels.
+ *
+ * @throws FileNotFoundException
+ * @throws \RuntimeException
+ *
+ * @return PromiseInterface
+ */
+ public function playFile(string $file, int $channels = 2): PromiseInterface
+ {
+ $deferred = new Deferred();
+ $notAValidFile = filter_var($file, FILTER_VALIDATE_URL) === false && ! file_exists($file);
+
+ if (
+ $notAValidFile || (! $this->ready) || $this->speaking
+ ) {
+ if ($notAValidFile) {
+ $deferred->reject(new FileNotFoundException("Could not find the file \"{$file}\"."));
+ }
+
+ if (! $this->ready) {
+ $deferred->reject(new ClientNotReadyException());
+ }
+
+ if ($this->speaking) {
+ $deferred->reject(new AudioAlreadyPlayingException());
+ }
+
+ return $deferred->promise();
+ }
+
+ $process = Ffmpeg::encode($file, volume: $this->getDbVolume());
+ $process->start();
+
+ return $this->playOggStream($process);
+ }
+
+ /**
+ * Plays a raw PCM16 stream.
+ *
+ * @param resource|Stream $stream The stream to be encoded and sent.
+ * @param int $channels How many audio channels the PCM16 was encoded with.
+ * @param int $audioRate Audio sampling rate the PCM16 was encoded with.
+ *
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException Thrown when the stream passed to playRawStream is not a valid resource.
+ *
+ * @return PromiseInterface
+ */
+ public function playRawStream($stream, int $channels = 2, int $audioRate = 48000): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ if (! $this->ready) {
+ $deferred->reject(new \RuntimeException('Voice Client is not ready.'));
+
+ return $deferred->promise();
+ }
+
+ if ($this->speaking) {
+ $deferred->reject(new \RuntimeException('Audio already playing.'));
+
+ return $deferred->promise();
+ }
+
+ if (! is_resource($stream) && ! $stream instanceof Stream) {
+ $deferred->reject(new \InvalidArgumentException('The stream passed to playRawStream was not an instance of resource or ReactPHP Stream.'));
+
+ return $deferred->promise();
+ }
+
+ if (is_resource($stream)) {
+ $stream = new Stream($stream);
+ }
+
+ $process = Ffmpeg::encode(volume: $this->getDbVolume(), preArgs: [
+ '-f', 's16le',
+ '-ac', $channels,
+ '-ar', $audioRate,
+ ]);
+ $process->start();
+ $stream->pipe($process->stdin);
+
+ return $this->playOggStream($process);
+ }
+
+ /**
+ * Plays an Ogg Opus stream.
+ *
+ * @param resource|Process|Stream $stream The Ogg Opus stream to be sent.
+ *
+ * @throws \RuntimeException
+ * @throws \InvalidArgumentException
+ *
+ * @return PromiseInterface
+ */
+ public function playOggStream($stream): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ if (! $this->isReady()) {
+ $deferred->reject(new \RuntimeException('Voice client is not ready yet.'));
+
+ return $deferred->promise();
+ }
+
+ if ($this->speaking) {
+ $deferred->reject(new \RuntimeException('Audio already playing.'));
+
+ return $deferred->promise();
+ }
+
+ if ($stream instanceof Process) {
+ $stream->stderr->on('data', function ($d) {
+ if (empty($d)) {
+ return;
+ }
+
+ $this->emit('stderr', [$d, $this]);
+ });
+
+ $stream = $stream->stdout;
+ }
+
+ if (is_resource($stream)) {
+ $stream = new Stream($stream);
+ }
+
+ if (! ($stream instanceof ReadableStreamInterface)) {
+ $deferred->reject(new \InvalidArgumentException('The stream passed to playOggStream was not an instance of resource, ReactPHP Process, ReactPHP Readable Stream'));
+
+ return $deferred->promise();
+ }
+
+ $this->buffer = new RealBuffer();
+ $stream->on('data', fn ($d) => $this->buffer->write($d));
+
+ /** @var OggStream */
+ $ogg = null;
+
+ $loops = 0;
+
+ $this->setSpeaking(self::MICROPHONE);
+
+ OggStream::fromBuffer($this->buffer)->then(function (OggStream $os) use ($deferred, &$ogg, &$loops) {
+ $ogg = $os;
+ $this->startTime = microtime(true) + 0.5;
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer(0.5, fn () => $this->readOggOpus($deferred, $ogg, $loops));
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Reads Ogg Opus packets and sends them to the voice server.
+ *
+ * @param Deferred $deferred The deferred promise.
+ * @param OggStream $ogg The Ogg stream to read packets from.
+ * @param int &$loops The number of loops that have been executed.
+ */
+ protected function readOggOpus(Deferred $deferred, OggStream &$ogg, int &$loops): void
+ {
+ $this->readOpusTimer = null;
+
+ $loops += 1;
+
+ // If the client is paused, delay by frame size and check again.
+ if ($this->paused) {
+ $this->udp->insertSilence();
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer($this->frameSize / 1000, fn () => $this->readOggOpus($deferred, $ogg, $loops));
+
+ return;
+ }
+
+ $ogg->getPacket()->then(function ($packet) use (&$loops, &$ogg, $deferred) {
+ // EOF for Ogg stream.
+ if (null === $packet) {
+ $this->reset();
+ $deferred->resolve(null);
+
+ return;
+ }
+
+ // increment sequence
+ // uint16 overflow protection
+ if (++$this->seq >= 2 ** 16) {
+ $this->seq = 0;
+ }
+
+ $this->udp->sendBuffer($packet);
+
+ // increment timestamp
+ // uint32 overflow protection
+ if (($this->timestamp += ($this->frameSize * 48)) >= 2 ** 32) {
+ $this->timestamp = 0;
+ }
+
+ $nextTime = $this->startTime + (20.0 / 1000.0) * $loops;
+ $delay = $nextTime - microtime(true);
+
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer($delay, fn () => $this->readOggOpus($deferred, $ogg, $loops));
+ }, function () use ($deferred) {
+ $this->reset();
+ $deferred->resolve(null);
+ });
+ }
+
+ /**
+ * Plays a DCA stream.
+ *
+ * @param resource|Process|Stream $stream The DCA stream to be sent.
+ *
+ * @return PromiseInterface
+ * @throws \Exception
+ *
+ * @deprecated 10.0.0 DCA is now deprecated in DiscordPHP, switch to using
+ * `playOggStream` with raw Ogg Opus.
+ */
+ public function playDCAStream($stream): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ if (! $this->isReady()) {
+ $deferred->reject(new \Exception('Voice client is not ready yet.'));
+
+ return $deferred->promise();
+ }
+
+ if ($this->speaking) {
+ $deferred->reject(new \Exception('Audio already playing.'));
+
+ return $deferred->promise();
+ }
+
+ if ($stream instanceof Process) {
+ $stream->stderr->on('data', function ($d) {
+ if (empty($d)) {
+ return;
+ }
+
+ $this->emit('stderr', [$d, $this]);
+ });
+
+ $stream = $stream->stdout;
+ }
+
+ if (is_resource($stream)) {
+ $stream = new Stream($stream, $this->discord->getLoop());
+ }
+
+ if (! ($stream instanceof ReadableStreamInterface)) {
+ $deferred->reject(new \Exception('The stream passed to playDCAStream was not an instance of resource, ReactPHP Process, ReactPHP Readable Stream'));
+
+ return $deferred->promise();
+ }
+
+ $this->buffer = new RealBuffer($this->discord->getLoop());
+ $stream->on('data', fn ($d) => $this->buffer->write($d));
+
+ $this->setSpeaking(self::MICROPHONE);
+
+ // Read magic byte header
+ $this->buffer->read(4)->then(function ($mb) {
+ if ($mb !== Dca::DCA_VERSION) {
+ throw new OutdatedDCAException('The DCA magic byte header was not correct.');
+ }
+
+ // Read JSON length
+ return $this->buffer->readInt32();
+ })->then(function ($jsonLength) {
+ // Read JSON content
+ return $this->buffer->read($jsonLength);
+ })->then(function ($metadata) use ($deferred) {
+ $metadata = json_decode($metadata, true);
+
+ if (null !== $metadata) {
+ $this->frameSize = $metadata['opus']['frame_size'] / 48;
+ }
+
+ $this->startTime = microtime(true) + 0.5;
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer(0.5, fn () => $this->readDCAOpus($deferred));
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Reads and processes a single Opus audio frame from a DCA (Discord Compressed Audio) stream.
+ *
+ * @param Deferred $deferred A promise that will be resolved when the reading process completes or fails.
+ */
+ protected function readDCAOpus(Deferred $deferred): void
+ {
+ $this->readOpusTimer = null;
+
+ // If the client is paused, delay by frame size and check again.
+ if ($this->paused) {
+ $this->udp->insertSilence();
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer($this->frameSize / 1000, fn () => $this->readDCAOpus($deferred));
+
+ return;
+ }
+
+ // Read opus length
+ $this->buffer->readInt16(1000)->then(function ($opusLength) {
+ // Read opus data
+ return $this->buffer->read($opusLength, null, 1000);
+ })->then(function ($opus) use ($deferred) {
+ $this->udp->sendBuffer($opus);
+
+ // increment sequence
+ // uint16 overflow protection
+ if (++$this->seq >= 2 ** 16) {
+ $this->seq = 0;
+ }
+
+ // increment timestamp
+ // uint32 overflow protection
+ if (($this->timestamp += ($this->frameSize * 48)) >= 2 ** 32) {
+ $this->timestamp = 0;
+ }
+
+ $this->readOpusTimer = $this->discord->getLoop()->addTimer(($this->frameSize - 1) / 1000, fn () => $this->readDCAOpus($deferred));
+ }, function () use ($deferred) {
+ $this->reset();
+ $deferred->resolve(null);
+ });
+ }
+
+ /**
+ * Resets the voice client.
+ */
+ protected function reset(): void
+ {
+ if ($this->readOpusTimer) {
+ $this->discord->getLoop()->cancelTimer($this->readOpusTimer);
+ $this->readOpusTimer = null;
+ }
+
+ $this->setSpeaking(self::NOT_SPEAKING);
+ $this->streamTime = 0;
+ $this->startTime = 0;
+ $this->paused = false;
+ $this->silenceRemaining = 5;
+ }
+
+ /**
+ * Sets the speaking value of the client.
+ *
+ * @param int $speaking Whether the client is speaking or not.
+ *
+ * @throws \RuntimeException
+ */
+ public function setSpeaking(int $speaking = self::MICROPHONE): void
+ {
+ if ($this->speaking === $speaking) {
+ return;
+ }
+
+ if (! $this->ready) {
+ throw new \RuntimeException('Voice Client is not ready.');
+ }
+
+ $this->ws->send(json_encode(VoicePayload::new(
+ Op::VOICE_SPEAKING,
+ [
+ 'speaking' => $speaking,
+ 'delay' => 0,
+ 'ssrc' => $this->ssrc,
+ ],
+ )));
+
+ $this->speaking = $speaking;
+ }
+
+ /**
+ * Switches voice channels.
+ *
+ * @param null|Channel $channel The channel to switch to.
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function switchChannel(?Channel $channel): self
+ {
+ if (isset($channel) && ! $channel->isVoiceBased()) {
+ throw new \InvalidArgumentException("Channel must be a voice channel to be able to switch, given type {$channel->type}.");
+ }
+
+ // We allow the user to switch to null, which will disconnect them from the voice channel.
+ if (! isset($channel)) {
+ $this->userClose = true;
+ } else {
+ $this->channel = $channel;
+ }
+
+ $this->mainSend(VoicePayload::new(
+ Op::OP_UPDATE_VOICE_STATE,
+ [
+ 'guild_id' => $this->channel->guild_id,
+ 'channel_id' => $channel?->id,
+ 'self_mute' => $this->mute,
+ 'self_deaf' => $this->deaf,
+ ],
+ ));
+
+ return $this;
+ }
+
+ /**
+ * Disconnects the discord from the current voice channel.
+ *
+ * @return \Discord\Voice\VoiceClient
+ */
+ public function disconnect(): static
+ {
+ $this->switchChannel(null);
+
+ return $this;
+ }
+
+ /**
+ * Sets the bitrate.
+ *
+ * @param int $bitrate The bitrate to set.
+ *
+ * @throws \DomainException
+ * @throws \RuntimeException
+ */
+ public function setBitrate(int $bitrate): void
+ {
+ if ($bitrate < 8000 || $bitrate > 384000) {
+ throw new \DomainException("{$bitrate} is not a valid option. The bitrate must be between 8,000 bps and 384,000 bps.");
+ }
+
+ /*if ($this->speaking) {
+ throw new \RuntimeException('Cannot change bitrate while playing.');
+ }*/
+
+ $this->bitrate = $bitrate;
+ }
+
+ /**
+ * Sets the volume.
+ *
+ * @param int $volume The volume to set.
+ *
+ * @throws \DomainException
+ * @throws \RuntimeException
+ */
+ public function setVolume(int $volume): void
+ {
+ if ($volume < 0 || $volume > 100) {
+ throw new \DomainException("{$volume}% is not a valid option. The bitrate must be between 0% and 100%.");
+ }
+
+ if ($this->speaking) {
+ throw new \RuntimeException('Cannot change volume while playing.');
+ }
+
+ $this->volume = $volume;
+ }
+
+ /**
+ * Sets the audio application.
+ *
+ * @param string $app The audio application to set.
+ *
+ * @throws \DomainException
+ * @throws \RuntimeException
+ */
+ public function setAudioApplication(string $app): void
+ {
+ $legal = ['voip', 'audio', 'lowdelay'];
+
+ if (! in_array($app, $legal)) {
+ throw new \DomainException("{$app} is not a valid option. Valid options are: ".implode(', ', $legal));
+ }
+
+ if ($this->speaking) {
+ throw new \RuntimeException('Cannot change audio application while playing.');
+ }
+
+ $this->audioApplication = $app;
+ }
+
+ /**
+ * Sends a message to the main websocket.
+ *
+ * @param Payload $data The data to send to the main WebSocket.
+ */
+ protected function mainSend($data): void
+ {
+ $this->discord->send($data);
+ }
+
+ /**
+ * Changes your mute and deaf value.
+ *
+ * @param bool $mute Whether you should be muted.
+ * @param bool $deaf Whether you should be deaf.
+ *
+ * @throws \RuntimeException
+ */
+ public function setMuteDeaf(bool $mute, bool $deaf): void
+ {
+ if (! $this->ready) {
+ throw new \RuntimeException('The voice client must be ready before you can set mute or deaf.');
+ }
+
+ $this->mute = $mute;
+ $this->deaf = $deaf;
+
+ $this->mainSend(VoicePayload::new(
+ Op::OP_UPDATE_VOICE_STATE,
+ [
+ 'guild_id' => $this->channel->guild_id,
+ 'channel_id' => $this->channel->id,
+ 'self_mute' => $mute,
+ 'self_deaf' => $deaf,
+ ],
+ ));
+
+ $this->udp->removeListener('message', [$this, 'handleAudioData']);
+
+ if (! $deaf) {
+ $this->udp->on('message', [$this, 'handleAudioData']);
+ }
+ }
+
+ /**
+ * Pauses the current sound.
+ *
+ * @throws \RuntimeException
+ */
+ public function pause(): void
+ {
+ if (! $this->speaking) {
+ throw new \RuntimeException('Audio must be playing to pause it.');
+ }
+
+ if ($this->paused) {
+ throw new \RuntimeException('Audio is already paused.');
+ }
+
+ $this->paused = true;
+ $this->udp->refreshSilenceFrames();
+ }
+
+ /**
+ * Unpauses the current sound.
+ *
+ * @throws \RuntimeException
+ */
+ public function unpause(): void
+ {
+ if (! $this->speaking) {
+ throw new \RuntimeException('Audio must be playing to unpause it.');
+ }
+
+ if (! $this->paused) {
+ throw new \RuntimeException('Audio is already playing.');
+ }
+
+ $this->paused = false;
+ $this->timestamp = microtime(true) * 1000;
+ }
+
+ /**
+ * Stops the current sound.
+ *
+ * @throws \RuntimeException
+ */
+ public function stop(): void
+ {
+ if (! $this->speaking) {
+ throw new \RuntimeException('Audio must be playing to stop it.');
+ }
+
+ if (isset($this->buffer)) {
+ $this->buffer->end();
+ }
+ $this->udp->insertSilence();
+ $this->reset();
+ }
+
+ /**
+ * Closes the voice client.
+ *
+ * @throws \RuntimeException
+ */
+ public function close(): void
+ {
+ if (! $this->ready) {
+ throw new \RuntimeException('Voice Client is not connected.');
+ }
+
+ if ($this->speaking) {
+ $this->stop();
+ $this->setSpeaking(self::NOT_SPEAKING);
+ }
+
+ $this->ready = false;
+
+ // Close processes for audio encoding
+ if (count($this?->voiceDecoders ?? []) > 0) {
+ foreach ($this->voiceDecoders as $decoder) {
+ $decoder->close();
+ }
+ }
+
+ if (count($this?->receiveStreams ?? []) > 0) {
+ foreach ($this->receiveStreams as $stream) {
+ $stream->close();
+ }
+ }
+
+ if (count($this->speakingStatus) > 0) {
+ foreach ($this->speakingStatus as $ss) {
+ $this->removeDecoder($ss);
+ }
+ }
+
+ // Only disconnect if we weren't disconnected by discord
+ if (! $this->udp->isClosed()) {
+ $this->disconnect();
+ }
+
+ $this->userClose = true;
+ $this->ws->close();
+ $this->udp->close();
+
+ $this->heartbeatInterval = null;
+
+ if (null !== $this->heartbeat) {
+ $this->discord->getLoop()->cancelTimer($this->heartbeat);
+ $this->heartbeat = null;
+ }
+
+ $this->seq = 0;
+ $this->timestamp = 0;
+ $this->sentLoginFrame = false;
+ $this->startTime = null;
+ $this->streamTime = 0;
+ $this->speakingStatus = Collection::for(Speaking::class, 'ssrc');
+
+ $this->emit('close');
+ }
+
+ /**
+ * Checks if the user is speaking.
+ *
+ * @param string|int|null $id Either the User ID or SSRC (if null, return discords speaking status).
+ *
+ * @return bool Whether the user is speaking.
+ */
+ public function isSpeaking($id = null): bool
+ {
+ return match (true) {
+ ! isset($id) => $this->speaking,
+ $user = $this->speakingStatus->get('user_id', $id) => $user->speaking,
+ $ssrc = $this->speakingStatus->get('ssrc', $id) => $ssrc->speaking,
+ default => false,
+ };
+ }
+
+ /**
+ * Handles a voice state update.
+ * NOTE: This object contains the data as the VoiceStateUpdate Part.
+ * @see \Discord\Parts\WebSockets\VoiceStateUpdate
+ *
+ * @param VoiceStateUpdate $data The WebSocket data.
+ */
+ public function handleVoiceStateUpdate(object $data): void
+ {
+ $ss = $this->speakingStatus->get('user_id', $data->user_id);
+
+ if (null === $ss) {
+ return; // not in our channel
+ }
+
+ if ($data->channel_id == $this->channel->id) {
+ return; // ignore, just a mute/deaf change
+ }
+
+ $this->removeDecoder($ss);
+ }
+
+ /**
+ * Handles a voice server change.
+ *
+ * @param array $data New voice server information.
+ */
+ public function handleVoiceServerChange(array $data = []): void
+ {
+ $this->discord->getLogger()->debug('voice server has changed, dynamically changing servers in the background', ['data' => $data]);
+ $this->reconnecting = true;
+ $this->sentLoginFrame = false;
+ $this->pause();
+
+ $this->close();
+ $this->ws->close();
+
+ $this->discord->getLoop()->cancelTimer($this->heartbeat);
+ $this->discord->getLoop()->cancelTimer($this->udp->heartbeat);
+
+ $this->on('resumed', function () {
+ $this->discord->getLogger()->debug('voice client resumed');
+ $this->unpause();
+ $this->speaking = self::NOT_SPEAKING;
+ $this->setSpeaking(self::MICROPHONE);
+ });
+
+ $data = array_merge($this->data, $data);
+ $this->data['token'] = $data['token']; // set the token if it changed
+ $this->endpoint = str_replace([':80', ':443'], '', $data['endpoint']);
+
+ WS::make($this, $this->discord, $data);
+ }
+
+ /**
+ * Removes and closes the voice decoder associated with the given SSRC.
+ *
+ * @param object $ss An object containing the SSRC (Synchronization Source identifier).
+ * Expected to have a property 'ssrc'.
+ */
+ protected function removeDecoder($ss): void
+ {
+ $decoder = $this->voiceDecoders[$ss->ssrc] ?? null;
+
+ if (null === $decoder) {
+ return; // no voice decoder to remove
+ }
+
+ $decoder->close();
+ unset(
+ $this->voiceDecoders[$ss->ssrc],
+ $this->speakingStatus[$ss->ssrc],
+ $this->receiveStreams[$ss->ssrc]
+ );
+ }
+
+ /**
+ * Gets a recieve voice stream.
+ *
+ * @param int|string $id Either a SSRC or User ID.
+ *
+ * @deprecated 10.5.0 Use getReceiveStream instead.
+ *
+ * @return RecieveStream|ReceiveStream|null
+ */
+ public function getRecieveStream($id)
+ {
+ return $this->getReceiveStream($id);
+ }
+
+ /**
+ * Gets a receive voice stream.
+ *
+ * @param int|string $id Either a SSRC or User ID.
+ *
+ * @return ReceiveStream|null
+ */
+ public function getReceiveStream($id)
+ {
+ if (isset($this->receiveStreams[$id])) {
+ return $this->receiveStreams[$id];
+ }
+
+ foreach ($this->speakingStatus as $status) {
+ if ($status?->user_id == $id) {
+ return $this->receiveStreams[$status?->ssrc];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Handles raw opus data from the UDP server.
+ *
+ * @param Packet|string $voicePacket The data from the UDP server.
+ */
+ public function handleAudioData(Packet|string $voicePacket): void
+ {
+ if (is_string($voicePacket)) {
+ $voicePacket = new Packet($voicePacket, key: $this->udp->ws->secretKey);
+
+ return;
+ }
+
+ if (! $this->shouldRecord) {
+ // If we are not recording, we don't need to handle audio data.
+ return;
+ }
+
+ $message = $voicePacket?->decryptedAudio ?? null;
+
+ if (! $message || ! $this->speakingStatus->get('ssrc', $voicePacket->getSSRC())) {
+ // We don't have a speaking status for this SSRC
+ // Probably a "ping" to the udp socket
+ // There's no message or the message threw an error inside the decrypt function
+ $this->discord->getLogger()->warning('No audio data.', ['voicePacket' => $voicePacket]);
+
+ return;
+ }
+
+ $this->emit('raw', [$message, $this]);
+
+ $ss = $this->speakingStatus->get('ssrc', $voicePacket->getSSRC());
+ /** @var Process */
+ $decoder = $this->voiceDecoders[$voicePacket->getSSRC()] ?? null;
+
+ if (null === $ss) {
+ // for some reason we don't have a speaking status
+ $this->discord->getLogger()->warning('Unknown SSRC.', ['ssrc' => $voicePacket->getSSRC(), 't' => $voicePacket->getTimestamp()]);
+
+ return;
+ }
+
+ if (null === $decoder) {
+ // make a decoder
+ if (! isset($this->receiveStreams[$ss->ssrc])) {
+ $this->receiveStreams[$ss->ssrc] = new ReceiveStream();
+
+ $this->receiveStreams[$ss->ssrc]->on('pcm', fn ($d) => $this->emit('channel-pcm', [$d, $this]));
+
+ $this->receiveStreams[$ss->ssrc]->on('opus', fn ($d) => $this->emit('channel-opus', [$d, $this]));
+ }
+
+ $this->createDecoder($ss);
+ /** @var Process */
+ $decoder = $this->voiceDecoders[$ss->ssrc] ?? null;
+ }
+
+ if ($decoder->stdin->isWritable() === false) {
+ $this->discord->getLogger()->warning('Decoder stdin is not writable.', ['ssrc' => $ss->ssrc]);
+
+ return; // decoder stdin is not writable, cannot write audio data.
+ // This should be either restarted or checked if the decoder is still running.
+ }
+
+ if (
+ empty($voicePacket->decryptedAudio)
+ || $voicePacket->decryptedAudio === "\xf8\xff\xfe" // Opus silence frame
+ || strlen($voicePacket->decryptedAudio) < 8 // Opus frame is at least 8 bytes
+ ) {
+ return; // no audio data to write
+ }
+
+ if (isset($this->opusdecoder)) {
+ $data = $this->opusdecoder->decode($voicePacket->decryptedAudio);
+
+ if (empty(trim($data))) {
+ $this->discord->getLogger()->debug('Received empty audio data.', ['ssrc' => $ss->ssrc]);
+
+ return; // no audio data to write
+ }
+
+ $decoder->stdin->write($data);
+ }
+ }
+
+ /**
+ * Creates and initializes a decoder process for the given stream session.
+ *
+ * @param object $ss The stream session object containing information such as SSRC and user ID.
+ */
+ protected function createDecoder($ss): void
+ {
+ $decoder = Ffmpeg::decode((string) $ss->ssrc);
+ $decoder->start();
+
+ $decoder->stdout->on('data', function ($data) use ($ss) {
+ if (empty($data)) {
+ return; // no data to process, should be ignored
+ }
+
+ // Emit the decoded opus data
+ $this->receiveStreams[$ss->ssrc]->writeOpus($data);
+ });
+
+ $decoder->stderr->on('data', function ($data) use ($ss) {
+ if (empty($data)) {
+ return; // no data to process
+ }
+
+ $this->emit("voice.{$ss->ssrc}.stderr", [$data, $this]);
+ $this->emit("voice.{$ss->user_id}.stderr", [$data, $this]);
+ });
+
+ // Store the decoder
+ $this->voiceDecoders[$ss->ssrc] = $decoder;
+
+ // Monitor the process for exit
+ $this->monitorProcessExit($decoder, $ss);
+ }
+
+ /**
+ * Monitor a process for exit and trigger callbacks when it exits.
+ *
+ * @param Process $process The process to monitor
+ * @param object $ss The speaking status object
+ * @param callable $createDecoder Function to create a new decoder if needed
+ */
+ protected function monitorProcessExit(Process $process, $ss): void
+ {
+ // Store the process ID
+ // $pid = $process->getPid();
+
+ // Check every second if the process is still running
+ $this->monitorProcessTimer = $this->discord->getLoop()->addPeriodicTimer(1.0, function () use ($process, $ss) {
+ // Check if the process is still running
+ if (! $process->isRunning()) {
+ // Get the exit code
+ $exitCode = $process->getExitCode();
+
+ // Clean up the timer
+ $this->discord->getLoop()->cancelTimer($this->monitorProcessTimer);
+
+ // If exit code indicates an error, emit event and recreate decoder
+ if ($exitCode > 0) {
+ $this->emit('decoder-error', [$exitCode, null, $ss]);
+ unset($this->voiceDecoders[$ss->ssrc]);
+ $this->createDecoder($ss);
+ }
+ }
+ });
+ }
+
+ /**
+ * Returns whether the voice client is ready.
+ *
+ * @return bool Whether the voice client is ready.
+ */
+ public function isReady(): bool
+ {
+ return $this->ready;
+ }
+
+ public function getDbVolume(): float|int
+ {
+ return match ($this->volume) {
+ 0 => -100,
+ 100 => 0,
+ default => -40 + ($this->volume / 100) * 40,
+ };
+ }
+
+ /**
+ * Creates a new voice client instance statically.
+ *
+ * @param \Discord\Discord $discord
+ * @param \Discord\Parts\Channel\Channel $channel
+ * @param array $data
+ * @param bool $deaf
+ * @param bool $mute
+ * @param null|Deferred $deferred
+ * @param null|Manager $manager
+ * @param bool $shouldBoot Whether the client should boot immediately.
+ *
+ * @return \Discord\Voice\Client
+ */
+ public static function make(): self
+ {
+ return new static(...func_get_args());
+ }
+
+ /**
+ * Boots the voice client and sets up event listeners.
+ *
+ * @return bool
+ */
+ public function boot(): bool
+ {
+ return $this->once('ready', function () {
+ $this->discord->getLogger()->info('voice client is ready');
+ if (isset($this->manager->clients[$this->channel->guild_id])) {
+ $this->disconnect();
+ }
+
+ $this->manager->clients[$this->channel->guild_id] = $this;
+
+ $this->setBitrate($this->channel->bitrate);
+
+ $this->discord->getLogger()->info('set voice client bitrate', ['bitrate' => $this->channel->bitrate]);
+ $this->deferred->resolve($this);
+ })
+ ->once('error', function ($e) {
+ $this->disconnect();
+ $this->discord->getLogger()->error('error initializing voice client', ['e' => $e->getMessage()]);
+ $this->deferred->reject($e);
+ })
+ ->once('close', function () {
+ $this->disconnect();
+ $this->discord->getLogger()->warning('voice client closed');
+ unset($this->manager->clients[$this->channel->guild_id]);
+ })
+ ->start();
+ }
+
+ public function record(): void
+ {
+ if ($this->shouldRecord) {
+ throw new \RuntimeException('Already recording audio.');
+ }
+
+ $this->shouldRecord = true;
+ $this->discord->getLogger()->info('Started recording audio.');
+
+ $this->udp->on('message', [$this, 'handleAudioData']);
+ }
+
+ public function stopRecording(): void
+ {
+ if (! $this->shouldRecord) {
+ throw new \RuntimeException('Not recording audio.');
+ }
+
+ $this->shouldRecord = false;
+ $this->discord->getLogger()->info('Stopped recording audio.');
+
+ $this->udp->removeListener('message', [$this, 'handleAudioData']);
+ $this->reset();
+
+ foreach ($this->voiceDecoders as $decoder) {
+ $decoder->close();
+ }
+
+ $this->voiceDecoders = [];
+ $this->receiveStreams = [];
+ $this->speakingStatus = Collection::for(Speaking::class, 'ssrc');
+ }
+
+ public function setData(array $data): self
+ {
+ $this->data = $data;
+
+ if (isset($this->data['token'], $this->data['endpoint'], $this->data['session'], $this->data['dnsConfig'])) {
+ $this->endpoint = str_replace([':80', ':443'], '', $this->data['endpoint']);
+ $this->dnsConfig = $this->data['dnsConfig'];
+ $this->data['user_id'] ??= $this->discord->id;
+ $this->boot();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Sets the Opus decoder.
+ *
+ * @param OpusDecoderInterface|null $opusdecoder The Opus decoder to set.
+ */
+ public function setDecoder(?OpusDecoderInterface $opusdecoder = null): void
+ {
+ $this->opusdecoder = $opusdecoder;
+ }
+}
diff --git a/vendor/discord-php-helpers/voice/src/Discord/WebSockets/OpEnum.php b/vendor/discord-php-helpers/voice/src/Discord/WebSockets/OpEnum.php
new file mode 100644
index 0000000..3b0c266
--- /dev/null
+++ b/vendor/discord-php-helpers/voice/src/Discord/WebSockets/OpEnum.php
@@ -0,0 +1,342 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\WebSockets;
+
+/**
+ * Contains constants used in websockets.
+ *
+ * @link https://discord.com/developers/docs/topics/opcodes-and-status-codes
+ * @see Discord\WebSockets\Op
+ *
+ * @since 3.2.1
+ */
+enum OpEnum: int
+{
+ /**
+ * Gateway Opcodes.
+ *
+ * All gateway events in Discord are tagged with an opcode that denotes the
+ * payload type. Your connection to our gateway may also sometimes close.
+ * When it does, you will receive a close code that tells you what happened.
+ *
+ * @link https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-opcodes
+ */
+
+ /** Dispatches an event. */
+ case OP_DISPATCH = 0;
+ /** Used for ping checking. */
+ case OP_HEARTBEAT = 1;
+ /** Used for client handshake. */
+ case OP_IDENTIFY = 2;
+ /** Used to update the client presence. */
+ case OP_PRESENCE_UPDATE = 3;
+ /** Used to join/move/leave voice channels. */
+ case OP_VOICE_STATE_UPDATE = 4;
+ /** Used for voice ping checking. */
+ case OP_VOICE_SERVER_PING = 5;
+ /** Used to resume a closed connection. */
+ case OP_RESUME = 6;
+ /** Used to redirect clients to a new gateway. */
+ case OP_RECONNECT = 7;
+ /** Used to request member chunks. */
+ case OP_GUILD_MEMBER_CHUNK = 8;
+ /** Used to notify clients when they have an invalid session. */
+ case OP_INVALID_SESSION = 9;
+ /** Used to pass through the heartbeat interval. */
+ case OP_HELLO = 10;
+ /** Used to acknowledge heartbeats. */
+ case OP_HEARTBEAT_ACK = 11;
+ /** Request soundboard sounds. */
+ case OP_REQUEST_SOUNDBOARD_SOUNDS = 31;
+
+ /**
+ * Voice Opcodes.
+ *
+ * Our voice gateways have their own set of opcodes and close codes.
+ *
+ * @link https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-opcodes
+ */
+
+ /** Used to begin a voice WebSocket connection. */
+ case VOICE_IDENTIFY = 0;
+ /** Used to select the voice protocol. */
+ case VOICE_SELECT_PROTO = 1;
+ /** Used to complete the WebSocket handshake. */
+ case VOICE_READY = 2;
+ /** Used to keep the WebSocket connection alive. */
+ case VOICE_HEARTBEAT = 3;
+ /** Used to describe the session. */
+ case VOICE_DESCRIPTION = 4;
+ /** Used to identify which users are speaking. */
+ case VOICE_SPEAKING = 5;
+ /** Sent by the Discord servers to acknowledge heartbeat */
+ case VOICE_HEARTBEAT_ACK = 6;
+ /** Resume a connection. */
+ case VOICE_RESUME = 7;
+ /** Hello packet used to pass heartbeat interval */
+ case VOICE_HELLO = 8;
+ /** Acknowledge a successful session resume. */
+ case VOICE_RESUMED = 9;
+ /** One or more clients have connected to the voice channel */
+ case VOICE_CLIENTS_CONNECT = 11;
+ case VOICE_CLIENT_CONNECT = 11; // Deprecated, used VOICE_CLIENTS_CONNECT instead
+ /** A client has disconnected from the voice channel. */
+ case VOICE_CLIENT_DISCONNECT = 13;
+ /** Was not documented within the op codes and statuses*/
+ case VOICE_CLIENT_UNKNOWN_15 = 15;
+ case VOICE_CLIENT_UNKNOWN_18 = 18;
+ /** NOT DOCUMENTED - Assumed to be the platform type in which the user is. */
+ case VOICE_CLIENT_PLATFORM = 20;
+ /** A downgrade from the DAVE protocol is upcoming. */
+ case VOICE_DAVE_PREPARE_TRANSITION = 21;
+ /** Execute a previously announced protocol transition. */
+ case VOICE_DAVE_EXECUTE_TRANSITION = 22;
+ /** Acknowledge readiness previously announced transition. */
+ case VOICE_DAVE_TRANSITION_READY = 23;
+ /** A DAVE protocol version or group change is upcoming. */
+ case VOICE_DAVE_PREPARE_EPOCH = 24;
+ /** Credential and public key for MLS external sender. */
+ case VOICE_DAVE_MLS_EXTERNAL_SENDER = 25;
+ /** MLS Key Package for pending group member. */
+ case VOICE_DAVE_MLS_KEY_PACKAGE = 26;
+ /** MLS Proposals to be appended or revoked. */
+ case VOICE_DAVE_MLS_PROPOSALS = 27;
+ /** MLS Commit with optional MLS Welcome messages. */
+ case VOICE_DAVE_MLS_COMMIT_WELCOME = 28;
+ /** MLS Commit to be processed for upcoming transition. */
+ case VOICE_DAVE_MLS_ANNOUNCE_COMMIT_TRANSITION = 29;
+ /** MLS Welcome to group for upcoming transition. */
+ case VOICE_DAVE_MLS_WELCOME = 30;
+ /** Flag invalid commit or welcome, request re-add */
+ case VOICE_DAVE_MLS_INVALID_COMMIT_WELCOME = 31;
+
+ /**
+ * Gateway Close Event Codes.
+ *
+ * In order to prevent broken reconnect loops, you should consider some
+ * close codes as a signal to stop reconnecting. This can be because your
+ * token expired, or your identification is invalid. This table explains
+ * what the application defined close codes for the gateway are, and which
+ * close codes you should not attempt to reconnect.
+ *
+ * @link https://discord.com/developers/docs/topics/opcodes-and-status-codes#gateway-gateway-close-event-codes
+ */
+
+ /** Normal close or heartbeat is invalid. */
+ case CLOSE_NORMAL = 1000;
+ /** Abnormal close. */
+ case CLOSE_ABNORMAL = 1006;
+ /** Unknown error. */
+ case CLOSE_UNKNOWN_ERROR = 4000;
+ /** Unknown opcode was sent. */
+ case CLOSE_INVALID_OPCODE = 4001;
+ /** Invalid message was sent. */
+ case CLOSE_INVALID_MESSAGE = 4002;
+ /** Not authenticated. */
+ case CLOSE_NOT_AUTHENTICATED = 4003;
+ /** Invalid token on IDENTIFY. */
+ case CLOSE_INVALID_TOKEN = 4004;
+ /** Already authenticated. */
+ case CONST_ALREADY_AUTHD = 4005;
+ /** Session is invalid. */
+ case CLOSE_INVALID_SESSION = 4006;
+ /** Invalid RESUME sequence. */
+ case CLOSE_INVALID_SEQ = 4007;
+ /** Too many messages sent. */
+ case CLOSE_TOO_MANY_MSG = 4008;
+ /** Session timeout. */
+ case CLOSE_SESSION_TIMEOUT = 4009;
+ /** Invalid shard. */
+ case CLOSE_INVALID_SHARD = 4010;
+ /** Sharding required. */
+ case CLOSE_SHARDING_REQUIRED = 4011;
+ /** Invalid API version. */
+ case CLOSE_INVALID_VERSION = 4012;
+ /** Invalid intents. */
+ case CLOSE_INVALID_INTENTS = 4013;
+ /** Disallowed intents. */
+ case CLOSE_DISALLOWED_INTENTS = 4014;
+
+ /**
+ * Voice Close Event Codes.
+ *
+ * @link https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes
+ */
+
+ /** Can't find the server. */
+ case CLOSE_VOICE_SERVER_NOT_FOUND = 4011;
+ /** Unknown protocol. */
+ case CLOSE_VOICE_UNKNOWN_PROTO = 4012;
+ /** Disconnected from channel. */
+ case CLOSE_VOICE_DISCONNECTED = 4014;
+ /** Voice server crashed. */
+ case CLOSE_VOICE_SERVER_CRASH = 4015;
+ /** Unknown encryption mode. */
+ case CLOSE_VOICE_UNKNOWN_ENCRYPT = 4016;
+
+ /**
+ * Returns the critical event codes that we should not reconnect after.
+ *
+ * @return array
+ */
+ public static function getCriticalCloseCodes(): array
+ {
+ return [
+ self::CLOSE_INVALID_TOKEN,
+ self::CLOSE_SHARDING_REQUIRED,
+ self::CLOSE_INVALID_SHARD,
+ self::CLOSE_INVALID_VERSION,
+ self::CLOSE_INVALID_INTENTS,
+ self::CLOSE_DISALLOWED_INTENTS,
+ ];
+ }
+
+ /**
+ * Returns the critical event codes for a voice websocket.
+ *
+ * @return array
+ */
+ public static function getCriticalVoiceCloseCodes(): array
+ {
+ return [
+ self::CLOSE_INVALID_SESSION,
+ self::CLOSE_INVALID_TOKEN,
+ self::CLOSE_VOICE_SERVER_NOT_FOUND,
+ self::CLOSE_VOICE_UNKNOWN_PROTO,
+ self::CLOSE_VOICE_UNKNOWN_ENCRYPT,
+ ];
+ }
+
+ public static function getVoiceCodes(): array
+ {
+ return [
+ self::VOICE_IDENTIFY,
+ self::VOICE_SELECT_PROTO,
+ self::VOICE_READY,
+ self::VOICE_HEARTBEAT,
+ self::VOICE_DESCRIPTION,
+ self::VOICE_SPEAKING,
+ self::VOICE_HEARTBEAT_ACK,
+ self::VOICE_RESUME,
+ self::VOICE_HELLO,
+ self::VOICE_RESUMED,
+ self::VOICE_CLIENTS_CONNECT,
+ self::VOICE_CLIENT_CONNECT,
+ self::VOICE_CLIENT_DISCONNECT,
+ self::VOICE_CLIENT_UNKNOWN_15,
+ self::VOICE_CLIENT_UNKNOWN_18,
+ self::VOICE_CLIENT_PLATFORM,
+ self::VOICE_DAVE_PREPARE_TRANSITION,
+ self::VOICE_DAVE_EXECUTE_TRANSITION,
+ self::VOICE_DAVE_TRANSITION_READY,
+ self::VOICE_DAVE_PREPARE_EPOCH,
+ self::VOICE_DAVE_MLS_EXTERNAL_SENDER,
+ self::VOICE_DAVE_MLS_KEY_PACKAGE,
+ self::VOICE_DAVE_MLS_PROPOSALS,
+ self::VOICE_DAVE_MLS_COMMIT_WELCOME,
+ self::VOICE_DAVE_MLS_ANNOUNCE_COMMIT_TRANSITION,
+ self::VOICE_DAVE_MLS_WELCOME,
+ self::VOICE_DAVE_MLS_INVALID_COMMIT_WELCOME,
+ ];
+ }
+
+ public static function getGatewayCodes(): array
+ {
+ return [
+ self::OP_DISPATCH,
+ self::OP_HEARTBEAT,
+ self::OP_IDENTIFY,
+ self::OP_PRESENCE_UPDATE,
+ self::OP_VOICE_STATE_UPDATE,
+ self::OP_VOICE_SERVER_PING,
+ self::OP_RESUME,
+ self::OP_RECONNECT,
+ self::OP_GUILD_MEMBER_CHUNK,
+ self::OP_INVALID_SESSION,
+ self::OP_HELLO,
+ self::OP_HEARTBEAT_ACK,
+ self::OP_REQUEST_SOUNDBOARD_SOUNDS,
+ ];
+ }
+
+ public static function getAllCodes(): array
+ {
+ return array_merge(
+ self::getGatewayCodes(),
+ self::getVoiceCodes()
+ );
+ }
+
+ public static function isVoiceCode(int $code): bool
+ {
+ return in_array($code, self::getVoiceCodes(), true);
+ }
+
+ public static function isGatewayCode(int $code): bool
+ {
+ return in_array($code, self::getGatewayCodes(), true);
+ }
+
+ public static function isValidCode(int $code): bool
+ {
+ return in_array($code, self::getAllCodes(), true);
+ }
+
+ public static function isCriticalCloseCode(int $code): bool
+ {
+ return in_array($code, self::getCriticalCloseCodes(), true);
+ }
+
+ public static function isCriticalVoiceCloseCode(int $code): bool
+ {
+ return in_array($code, self::getCriticalVoiceCloseCodes(), true);
+ }
+
+ public static function isValidOpCode(int $code): bool
+ {
+ return self::isGatewayCode($code) || self::isVoiceCode($code);
+ }
+
+ public static function isValidCloseCode(int $code): bool
+ {
+ return self::isCriticalCloseCode($code) || self::isCriticalVoiceCloseCode($code);
+ }
+
+ public static function isValidOp(int $code): bool
+ {
+ return self::isValidOpCode($code) || self::isValidCloseCode($code);
+ }
+
+ public static function voiceCodeToString(
+ ?self $code = null,
+ bool $snakeCase = false,
+ bool $pluckVoicePrefix = true
+ ): string {
+ $code ??= $code?->value;
+ if (! $code instanceof self && ! self::isVoiceCode($code)) {
+ return '';
+ }
+ $name = self::from($code)->name;
+
+ if ($pluckVoicePrefix) {
+ $name = str_replace('VOICE_', '', $name);
+ }
+
+ if ($snakeCase) {
+ return strtolower(preg_replace('/(?
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE.md file.
+ */
+
+namespace Discord\WebSockets;
+
+/**
+ * Represents a Gateway event payload with a voice token.
+ *
+ * Gateway event payloads have a common structure, but the contents of the associated data (d) varies between the different events.
+ *
+ * @link https://discord.com/developers/docs/topics/voice-connections#retrieving-voice-server-information-example-voice-server-update-payload
+ *
+ * @property token
+ */
+class VoicePayload extends Payload
+{
+ /** @var string|null */
+ protected $token;
+
+ public function __construct(int $op, $d = null, ?int $s = null, ?string $t = null, ?string $token = null)
+ {
+ $this->op = $op;
+ $this->d = $d;
+ $this->s = $s;
+ $this->t = $t;
+ $this->token = $token;
+ }
+
+ public static function new(
+ int $op,
+ $d = null,
+ ?int $s = null,
+ ?string $t = null,
+ ?string $token = null
+ ): self {
+ return new self($op, $d, $s, $t, $token);
+ }
+
+ public function setToken(?string $token = null): self
+ {
+ $this->token = $token;
+
+ return $this;
+ }
+
+ public function getToken(): ?string
+ {
+ return $this->token ?? null;
+ }
+
+ public function jsonSerialize(): array
+ {
+ $data = parent::jsonSerialize();
+
+ if (isset($this->token)) {
+ $data['d']['token'] = $this->token;
+ }
+
+ return $data;
+ }
+
+ public function __debugInfo()
+ {
+ $array = parent::__debugInfo();
+
+ if (isset($array['token'])) {
+ $array['token'] = 'xxxxx';
+ }
+
+ return $array;
+ }
+}
diff --git a/vendor/discord-php/http/.gitignore b/vendor/discord-php/http/.gitignore
new file mode 100644
index 0000000..33654ba
--- /dev/null
+++ b/vendor/discord-php/http/.gitignore
@@ -0,0 +1,9 @@
+/vendor/
+composer.lock
+test.php
+.php_cs.cache
+.php_cs
+.php-cs-fixer.php
+.php-cs-fixer.cache
+.vscode
+.phpunit.cache
diff --git a/vendor/discord-php/http/.php-cs-fixer.dist.php b/vendor/discord-php/http/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..dc26d65
--- /dev/null
+++ b/vendor/discord-php/http/.php-cs-fixer.dist.php
@@ -0,0 +1,102 @@
+
+
+This file is subject to the MIT license that is bundled
+with this source code in the LICENSE file.
+EOF;
+
+$fixers = [
+ 'blank_line_after_namespace',
+ 'braces',
+ 'class_definition',
+ 'elseif',
+ 'encoding',
+ 'full_opening_tag',
+ 'function_declaration',
+ 'lowercase_keywords',
+ 'method_argument_space',
+ 'no_closing_tag',
+ 'no_spaces_after_function_name',
+ 'no_spaces_inside_parenthesis',
+ 'no_trailing_whitespace',
+ 'no_trailing_whitespace_in_comment',
+ 'single_blank_line_at_eof',
+ 'single_class_element_per_statement',
+ 'single_import_per_statement',
+ 'single_line_after_imports',
+ 'switch_case_semicolon_to_colon',
+ 'switch_case_space',
+ 'visibility_required',
+ 'blank_line_after_opening_tag',
+ 'no_multiline_whitespace_around_double_arrow',
+ 'no_empty_statement',
+ 'include',
+ 'no_trailing_comma_in_list_call',
+ 'not_operator_with_successor_space',
+ 'no_leading_namespace_whitespace',
+ 'no_blank_lines_after_class_opening',
+ 'no_blank_lines_after_phpdoc',
+ 'object_operator_without_whitespace',
+ 'binary_operator_spaces',
+ 'phpdoc_indent',
+ 'general_phpdoc_tag_rename',
+ 'phpdoc_inline_tag_normalizer',
+ 'phpdoc_tag_type',
+ 'phpdoc_no_access',
+ 'phpdoc_no_package',
+ 'phpdoc_scalar',
+ 'phpdoc_summary',
+ 'phpdoc_to_comment',
+ 'phpdoc_trim',
+ 'phpdoc_var_without_name',
+ 'no_leading_import_slash',
+ 'no_trailing_comma_in_singleline_array',
+ 'single_blank_line_before_namespace',
+ 'single_quote',
+ 'no_singleline_whitespace_before_semicolons',
+ 'cast_spaces',
+ 'standardize_not_equals',
+ 'ternary_operator_spaces',
+ 'trim_array_spaces',
+ 'unary_operator_spaces',
+ 'no_unused_imports',
+ 'no_useless_else',
+ 'no_useless_return',
+ 'phpdoc_no_empty_return',
+ 'no_extra_blank_lines',
+ 'multiline_whitespace_before_semicolons',
+];
+
+$rules = [
+ 'concat_space' => ['spacing' => 'none'],
+ 'phpdoc_no_alias_tag' => ['replacements' => ['type' => 'var']],
+ 'array_syntax' => ['syntax' => 'short'],
+ 'binary_operator_spaces' => ['align_double_arrow' => true, 'align_equals' => true],
+ 'header_comment' => ['header' => $header],
+ 'indentation_type' => true,
+ 'phpdoc_align' => [
+ 'align' => 'vertical',
+ 'tags' => ['param', 'property', 'property-read', 'property-write', 'return', 'throws', 'type', 'var', 'method'],
+ ],
+ 'blank_line_before_statement' => ['statements' => ['return']],
+ 'constant_case' => ['case' => 'lower'],
+ 'echo_tag_syntax' => ['format' => 'long'],
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
+];
+
+foreach ($fixers as $fix) {
+ $rules[$fix] = true;
+}
+
+$config = new PhpCsFixer\Config();
+
+return $config
+ ->setRules($rules)
+ ->setFinder(
+ PhpCsFixer\Finder::create()
+ ->in(__DIR__)
+ );
diff --git a/vendor/discord-php/http/LICENSE b/vendor/discord-php/http/LICENSE
new file mode 100644
index 0000000..1c4ec6b
--- /dev/null
+++ b/vendor/discord-php/http/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2021-present David Cole and all
+contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/discord-php/http/README.md b/vendor/discord-php/http/README.md
new file mode 100644
index 0000000..966fb88
--- /dev/null
+++ b/vendor/discord-php/http/README.md
@@ -0,0 +1,92 @@
+# DiscordPHP-Http
+
+Asynchronous HTTP client used for communication with the Discord REST API.
+
+## Requirements
+
+- PHP >=7.4
+
+## Installation
+
+```sh
+$ composer require discord-php/http
+```
+
+A [psr/log](https://packagist.org/packages/psr/log)-compliant logging library is also required. We recommend [monolog](https://github.com/Seldaek/monolog) which will be used in examples.
+
+## Usage
+
+```php
+pushHandler(new StreamHandler('php://output'));
+$http = new Http(
+ 'Bot xxxx.yyyy.zzzz',
+ $loop,
+ $logger
+);
+
+// set up a driver - this example uses the React driver
+$driver = new React($loop);
+$http->setDriver($driver);
+
+// must be the last line
+$loop->run();
+```
+
+All request methods have the same footprint:
+
+```php
+$http->get(string $url, $content = null, array $headers = []);
+$http->post(string $url, $content = null, array $headers = []);
+$http->put(string $url, $content = null, array $headers = []);
+$http->patch(string $url, $content = null, array $headers = []);
+$http->delete(string $url, $content = null, array $headers = []);
+```
+
+For other methods:
+
+```php
+$http->queueRequest(string $method, string $url, $content, array $headers = []);
+```
+
+All methods return the decoded JSON response in an object:
+
+```php
+// https://discord.com/api/v8/oauth2/applications/@me
+$http->get('oauth2/applications/@me')->done(function ($response) {
+ var_dump($response);
+}, function ($e) {
+ echo "Error: ".$e->getMessage().PHP_EOL;
+});
+```
+
+Most Discord endpoints are provided in the [Endpoint.php](src/Discord/Endpoint.php) class as constants. Parameters start with a colon,
+e.g. `channels/:channel_id/messages/:message_id`. You can bind parameters to then with the same class:
+
+```php
+// channels/channel_id_here/messages/message_id_here
+$endpoint = Endpoint::bind(Endpoint::CHANNEL_MESSAGE, 'channel_id_here', 'message_id_here');
+
+$http->get($endpoint)->done(...);
+```
+
+It is recommended that if the endpoint contains parameters you use the `Endpoint::bind()` function to sort requests into their correct rate limit buckets.
+For an example, see [DiscordPHP](https://github.com/discord-php/DiscordPHP).
+
+## License
+
+This software is licensed under the MIT license which can be viewed in the [LICENSE](LICENSE) file.
+
+## Credits
+
+- [David Cole](mailto:david.cole1340@gmail.com)
+- All contributors
diff --git a/vendor/discord-php/http/composer.json b/vendor/discord-php/http/composer.json
new file mode 100644
index 0000000..e10fe6d
--- /dev/null
+++ b/vendor/discord-php/http/composer.json
@@ -0,0 +1,43 @@
+{
+ "name": "discord-php/http",
+ "description": "Handles HTTP requests to Discord servers",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "David Cole",
+ "email": "david.cole1340@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {
+ "Discord\\Http\\": "src/Discord",
+ "Tests\\Discord\\Http\\": "tests/Discord"
+ }
+ },
+ "require": {
+ "php": "^7.4|^8.0",
+ "react/http": "^1.2",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "react/promise": "^2.2 || ^3.0.0"
+ },
+ "suggest": {
+ "guzzlehttp/guzzle": "For alternative to ReactPHP/Http Browser"
+ },
+ "require-dev": {
+ "monolog/monolog": "^2.2",
+ "friendsofphp/php-cs-fixer": "^2.17",
+ "psy/psysh": "^0.10.6",
+ "guzzlehttp/guzzle": "^6.0|^7.0",
+ "phpunit/phpunit": "^9.5",
+ "mockery/mockery": "^1.5",
+ "react/async": "^4 || ^3"
+ },
+ "scripts": {
+ "test": "php tests/Drivers/_server.php& HTTP_SERVER_PID=$!; ./vendor/bin/phpunit; kill $HTTP_SERVER_PID;",
+ "test-discord": "./vendor/bin/phpunit --testsuite Discord",
+ "test-drivers": "php tests/Drivers/_server.php& HTTP_SERVER_PID=$!; ./vendor/bin/phpunit --testsuite Drivers; kill $HTTP_SERVER_PID;",
+ "test-coverage": "php tests/Drivers/_server.php& HTTP_SERVER_PID=$!; php -d xdebug.mode=coverage ./vendor/bin/phpunit --coverage-text; kill $HTTP_SERVER_PID;",
+ "test-coverage-html": "php tests/Drivers/_server.php& HTTP_SERVER_PID=$!; php -d xdebug.mode=coverage ./vendor/bin/phpunit --coverage-html .phpunit.cache/cov-html; kill $HTTP_SERVER_PID;"
+ }
+}
diff --git a/vendor/discord-php/http/examples/file-upload.php b/vendor/discord-php/http/examples/file-upload.php
new file mode 100644
index 0000000..23175dc
--- /dev/null
+++ b/vendor/discord-php/http/examples/file-upload.php
@@ -0,0 +1,66 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+use Discord\Http\Drivers\Guzzle;
+use Discord\Http\Endpoint;
+use Discord\Http\Http;
+use Discord\Http\Multipart\MultipartBody;
+use Discord\Http\Multipart\MultipartField;
+use Psr\Log\NullLogger;
+use React\EventLoop\Loop;
+
+require './vendor/autoload.php';
+
+$http = new Http(
+ 'Your token',
+ Loop::get(),
+ new NullLogger(),
+ new Guzzle(
+ Loop::get()
+ )
+);
+
+$jsonPayloadField = new MultipartField(
+ 'json_payload',
+ json_encode([
+ 'content' => 'Hello!',
+ ]),
+ ['Content-Type' => 'application/json']
+);
+
+$imageField = new MultipartField(
+ 'files[0]',
+ file_get_contents('/path/to/image.png'),
+ ['Content-Type' => 'image/png'],
+ 'image.png'
+);
+
+$multipart = new MultipartBody([
+ $jsonPayloadField,
+ $imageField,
+]);
+
+$http->post(
+ Endpoint::bind(
+ Endpoint::CHANNEL_MESSAGES,
+ 'Channel ID'
+ ),
+ $multipart
+)->then(
+ function ($response) {
+ // Do something with response..
+ },
+ function (Exception $e) {
+ echo $e->getMessage(), PHP_EOL;
+ }
+);
+
+Loop::run();
diff --git a/vendor/discord-php/http/phpunit.xml b/vendor/discord-php/http/phpunit.xml
new file mode 100644
index 0000000..a7399c7
--- /dev/null
+++ b/vendor/discord-php/http/phpunit.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ tests/Discord
+
+
+
+ tests/Drivers
+
+
+
+
+
+ src
+
+
+
diff --git a/vendor/discord-php/http/src/Discord/Bucket.php b/vendor/discord-php/http/src/Discord/Bucket.php
new file mode 100644
index 0000000..ce78eb3
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Bucket.php
@@ -0,0 +1,226 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use Composer\InstalledVersions;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Log\LoggerInterface;
+use React\EventLoop\LoopInterface;
+use React\EventLoop\TimerInterface;
+use SplQueue;
+
+/**
+ * Represents a rate-limit bucket.
+ *
+ * @author David Cole
+ */
+class Bucket
+{
+ /**
+ * Request queue.
+ *
+ * @var SplQueue
+ */
+ protected $queue;
+
+ /**
+ * Bucket name.
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * ReactPHP event loop.
+ *
+ * @var LoopInterface
+ */
+ protected $loop;
+
+ /**
+ * HTTP logger.
+ *
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * Callback for when a request is ready.
+ *
+ * @var callable
+ */
+ protected $runRequest;
+
+ /**
+ * Whether we are checking the queue.
+ *
+ * @var bool
+ */
+ protected $checkerRunning = false;
+
+ /**
+ * Number of requests allowed before reset.
+ *
+ * @var int
+ */
+ protected $requestLimit;
+
+ /**
+ * Number of remaining requests before reset.
+ *
+ * @var int
+ */
+ protected $requestRemaining;
+
+ /**
+ * Timer to reset the bucket.
+ *
+ * @var TimerInterface
+ */
+ protected $resetTimer;
+
+ /**
+ * Whether react/promise v3 is used, if false, using v2.
+ */
+ protected $promiseV3 = true;
+
+ /**
+ * Bucket constructor.
+ *
+ * @param string $name
+ * @param callable $runRequest
+ */
+ public function __construct(string $name, LoopInterface $loop, LoggerInterface $logger, callable $runRequest)
+ {
+ $this->queue = new SplQueue;
+ $this->name = $name;
+ $this->loop = $loop;
+ $this->logger = $logger;
+ $this->runRequest = $runRequest;
+
+ $this->promiseV3 = str_starts_with(InstalledVersions::getVersion('react/promise'), '3.');
+ }
+
+ /**
+ * Enqueue a request.
+ *
+ * @param Request $request
+ */
+ public function enqueue(Request $request)
+ {
+ $this->queue->enqueue($request);
+ $this->logger->debug($this.' queued '.$request);
+ $this->checkQueue();
+ }
+
+ /**
+ * Checks for requests in the bucket.
+ */
+ public function checkQueue()
+ {
+ // We are already checking the queue.
+ if ($this->checkerRunning) {
+ return;
+ }
+
+ $this->checkerRunning = true;
+ $this->__checkQueue();
+ }
+
+ protected function __checkQueue()
+ {
+ // Check for rate-limits
+ if ($this->requestRemaining < 1 && ! is_null($this->requestRemaining)) {
+ $interval = 0;
+ if ($this->resetTimer) {
+ $interval = $this->resetTimer->getInterval() ?? 0;
+ }
+ $this->logger->info($this.' expecting rate limit, timer interval '.($interval * 1000).' ms');
+ $this->checkerRunning = false;
+
+ return;
+ }
+
+ // Queue is empty, job done.
+ if ($this->queue->isEmpty()) {
+ $this->checkerRunning = false;
+
+ return;
+ }
+
+ /** @var Request */
+ $request = $this->queue->dequeue();
+
+ // Promises v3 changed `->then` to behave as `->done` and removed `->then`. We still need the behaviour of `->done` in projects using v2
+ ($this->runRequest)($request)->{$this->promiseV3 ? 'then' : 'done'}(function (ResponseInterface $response) {
+ $resetAfter = (float) $response->getHeaderLine('X-Ratelimit-Reset-After');
+ $limit = $response->getHeaderLine('X-Ratelimit-Limit');
+ $remaining = $response->getHeaderLine('X-Ratelimit-Remaining');
+
+ if ($resetAfter) {
+ $resetAfter = (float) $resetAfter;
+
+ if ($this->resetTimer) {
+ $this->loop->cancelTimer($this->resetTimer);
+ }
+
+ $this->resetTimer = $this->loop->addTimer($resetAfter, function () {
+ // Reset requests remaining and check queue
+ $this->requestRemaining = $this->requestLimit;
+ $this->resetTimer = null;
+ $this->checkQueue();
+ });
+ }
+
+ // Check if rate-limit headers are present and store
+ if (is_numeric($limit)) {
+ $this->requestLimit = (int) $limit;
+ }
+
+ if (is_numeric($remaining)) {
+ $this->requestRemaining = (int) $remaining;
+ }
+
+ // Check for more requests
+ $this->__checkQueue();
+ }, function ($rateLimit) use ($request) {
+ if ($rateLimit instanceof RateLimit) {
+ $this->queue->enqueue($request);
+
+ // Bucket-specific rate-limit
+ // Re-queue the request and wait the retry after time
+ if (! $rateLimit->isGlobal()) {
+ $this->loop->addTimer($rateLimit->getRetryAfter(), fn () => $this->__checkQueue());
+ }
+ // Stop the queue checker for a global rate-limit.
+ // Will be restarted when global rate-limit finished.
+ else {
+ $this->checkerRunning = false;
+
+ $this->logger->debug($this.' stopping queue checker');
+ }
+ } else {
+ $this->__checkQueue();
+ }
+ });
+ }
+
+ /**
+ * Converts a bucket to a user-readable string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return 'BUCKET '.$this->name;
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/DriverInterface.php b/vendor/discord-php/http/src/Discord/DriverInterface.php
new file mode 100644
index 0000000..4389149
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/DriverInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use Psr\Http\Message\ResponseInterface;
+use React\Promise\PromiseInterface;
+
+/**
+ * Interface for an HTTP driver.
+ *
+ * @author David Cole
+ */
+interface DriverInterface
+{
+ /**
+ * Runs a request.
+ *
+ * Returns a promise resolved with a PSR response interface.
+ *
+ * @param Request $request
+ *
+ * @return PromiseInterface
+ */
+ public function runRequest(Request $request): PromiseInterface;
+}
diff --git a/vendor/discord-php/http/src/Discord/Drivers/Guzzle.php b/vendor/discord-php/http/src/Discord/Drivers/Guzzle.php
new file mode 100644
index 0000000..17b9bb6
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Drivers/Guzzle.php
@@ -0,0 +1,77 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Drivers;
+
+use Discord\Http\DriverInterface;
+use Discord\Http\Request;
+use GuzzleHttp\Client;
+use GuzzleHttp\RequestOptions;
+use React\EventLoop\LoopInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+
+/**
+ * guzzlehttp/guzzle driver for Discord HTTP client. (still with React Promise).
+ *
+ * @author SQKo
+ */
+class Guzzle implements DriverInterface
+{
+ /**
+ * ReactPHP event loop.
+ *
+ * @var LoopInterface|null
+ */
+ protected $loop;
+
+ /**
+ * GuzzleHTTP/Guzzle client.
+ *
+ * @var Client
+ */
+ protected $client;
+
+ /**
+ * Constructs the Guzzle driver.
+ *
+ * @param LoopInterface|null $loop
+ * @param array $options
+ */
+ public function __construct(?LoopInterface $loop = null, array $options = [])
+ {
+ $this->loop = $loop;
+
+ // Allow 400 and 500 HTTP requests to be resolved rather than rejected.
+ $options['http_errors'] = false;
+ $this->client = new Client($options);
+ }
+
+ public function runRequest(Request $request): PromiseInterface
+ {
+ // Create a React promise
+ $deferred = new Deferred();
+ $reactPromise = $deferred->promise();
+
+ $promise = $this->client->requestAsync($request->getMethod(), $request->getUrl(), [
+ RequestOptions::HEADERS => $request->getHeaders(),
+ RequestOptions::BODY => $request->getContent(),
+ ])->then([$deferred, 'resolve'], [$deferred, 'reject']);
+
+ if ($this->loop) {
+ $this->loop->futureTick([$promise, 'wait']);
+ } else {
+ $promise->wait();
+ }
+
+ return $reactPromise;
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Drivers/React.php b/vendor/discord-php/http/src/Discord/Drivers/React.php
new file mode 100644
index 0000000..9103e34
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Drivers/React.php
@@ -0,0 +1,72 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Drivers;
+
+use Discord\Http\DriverInterface;
+use Discord\Http\Request;
+use React\EventLoop\LoopInterface;
+use React\Http\Browser;
+use React\Promise\PromiseInterface;
+use React\Socket\Connector;
+
+/**
+ * react/http driver for Discord HTTP client.
+ *
+ * @author David Cole
+ */
+class React implements DriverInterface
+{
+ /**
+ * ReactPHP event loop.
+ *
+ * @var LoopInterface
+ */
+ protected $loop;
+
+ /**
+ * ReactPHP/HTTP browser.
+ *
+ * @var Browser
+ */
+ protected $browser;
+
+ /**
+ * Constructs the React driver.
+ *
+ * @param LoopInterface $loop
+ * @param array $options
+ */
+ public function __construct(LoopInterface $loop, array $options = [])
+ {
+ $this->loop = $loop;
+
+ // Allow 400 and 500 HTTP requests to be resolved rather than rejected.
+ $browser = new Browser($loop, new Connector($loop, $options));
+ $this->browser = $browser->withRejectErrorResponse(false);
+ }
+
+ /**
+ * Runs the request using the React HTTP client.
+ *
+ * @param Request $request The request to run.
+ *
+ * @return PromiseInterface
+ */
+ public function runRequest($request): PromiseInterface
+ {
+ return $this->browser->{$request->getMethod()}(
+ $request->getUrl(),
+ $request->getHeaders(),
+ $request->getContent()
+ );
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Endpoint.php b/vendor/discord-php/http/src/Discord/Endpoint.php
new file mode 100644
index 0000000..4ce215b
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Endpoint.php
@@ -0,0 +1,380 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+class Endpoint implements EndpointInterface
+{
+ use EndpointTrait;
+
+ // GET
+ public const GATEWAY = 'gateway';
+ // GET
+ public const GATEWAY_BOT = self::GATEWAY.'/bot';
+
+ // GET
+ public const APPLICATION_SKUS = 'applications/:application_id/skus';
+ // GET, POST
+ public const APPLICATION_EMOJIS = 'applications/:application_id/emojis';
+ // GET, PATCH, DELETE
+ public const APPLICATION_EMOJI = 'applications/:application_id/emojis/:emoji_id';
+ // GET, POST
+ public const APPLICATION_ENTITLEMENTS = 'applications/:application_id/entitlements';
+ // DELETE
+ public const APPLICATION_ENTITLEMENT = self::APPLICATION_ENTITLEMENTS.'/:entitlement_id';
+ // POST
+ public const APPLICATION_ENTITLEMENT_CONSUME = self::APPLICATION_ENTITLEMENT.'/consume';
+ // GET, POST, PUT
+ public const GLOBAL_APPLICATION_COMMANDS = 'applications/:application_id/commands';
+ // GET, PATCH, DELETE
+ public const GLOBAL_APPLICATION_COMMAND = self::GLOBAL_APPLICATION_COMMANDS.'/:command_id';
+ // GET, POST, PUT
+ public const GUILD_APPLICATION_COMMANDS = 'applications/:application_id/guilds/:guild_id/commands';
+ // GET, PUT
+ public const GUILD_APPLICATION_COMMANDS_PERMISSIONS = self::GUILD_APPLICATION_COMMANDS.'/permissions';
+ // GET, PATCH, DELETE
+ public const GUILD_APPLICATION_COMMAND = self::GUILD_APPLICATION_COMMANDS.'/:command_id';
+ // GET, PUT
+ public const GUILD_APPLICATION_COMMAND_PERMISSIONS = self::GUILD_APPLICATION_COMMANDS.'/:command_id/permissions';
+ // POST
+ public const INTERACTION_RESPONSE = 'interactions/:interaction_id/:interaction_token/callback';
+ // POST
+ public const CREATE_INTERACTION_FOLLOW_UP = 'webhooks/:application_id/:interaction_token';
+ // PATCH, DELETE
+ public const ORIGINAL_INTERACTION_RESPONSE = self::CREATE_INTERACTION_FOLLOW_UP.'/messages/@original';
+ // PATCH, DELETE
+ public const INTERACTION_FOLLOW_UP = self::CREATE_INTERACTION_FOLLOW_UP.'/messages/:message_id';
+
+ // GET
+ public const SKU_SUBSCRIPTIONS = '/skus/:sku_id/subscriptions';
+ // GET
+ public const SKU_SUBSCRIPTION = self::SKU_SUBSCRIPTIONS.'/:subscription_id';
+
+ // GET
+ public const AUDIT_LOG = 'guilds/:guild_id/audit-logs';
+
+ // GET, PATCH, DELETE
+ public const CHANNEL = 'channels/:channel_id';
+ // GET, POST
+ public const CHANNEL_MESSAGES = self::CHANNEL.'/messages';
+ // GET, PATCH, DELETE
+ public const CHANNEL_MESSAGE = self::CHANNEL.'/messages/:message_id';
+ // POST
+ public const CHANNEL_CROSSPOST_MESSAGE = self::CHANNEL.'/messages/:message_id/crosspost';
+ // POST
+ public const CHANNEL_MESSAGES_BULK_DELETE = self::CHANNEL.'/messages/bulk-delete';
+ // PUT, DELETE
+ public const CHANNEL_PERMISSIONS = self::CHANNEL.'/permissions/:overwrite_id';
+ // GET, POST
+ public const CHANNEL_INVITES = self::CHANNEL.'/invites';
+ // POST
+ public const CHANNEL_FOLLOW = self::CHANNEL.'/followers';
+ // POST
+ public const CHANNEL_TYPING = self::CHANNEL.'/typing';
+ // GET
+ /** @deprecated Use `CHANNEL_MESSAGES_PINS` */
+ public const CHANNEL_PINS = self::CHANNEL.'/pins';
+ // PUT, DELETE
+ /** @deprecated Use `CHANNEL_MESSAGES_PINS` */
+ public const CHANNEL_PIN = self::CHANNEL.'/pins/:message_id';
+ // GET
+ public const CHANNEL_MESSAGES_PINS = self::CHANNEL.'/messages/pins';
+ // PUT, DELETE
+ public const CHANNEL_MESSAGES_PIN = self::CHANNEL.'/messages/pins/:message_id';
+ // POST
+ public const CHANNEL_THREADS = self::CHANNEL.'/threads';
+ // POST
+ public const CHANNEL_MESSAGE_THREADS = self::CHANNEL_MESSAGE.'/threads';
+ // GET
+ public const CHANNEL_THREADS_ARCHIVED_PUBLIC = self::CHANNEL_THREADS.'/archived/public';
+ // GET
+ public const CHANNEL_THREADS_ARCHIVED_PRIVATE = self::CHANNEL_THREADS.'/archived/private';
+ // GET
+ public const CHANNEL_THREADS_ARCHIVED_PRIVATE_ME = self::CHANNEL.'/users/@me/threads/archived/private';
+ // POST
+ public const CHANNEL_SEND_SOUNDBOARD_SOUND = self::CHANNEL.'/send-soundboard-sound';
+
+ // GET, PATCH, DELETE
+ public const THREAD = 'channels/:thread_id';
+ // GET
+ public const THREAD_MEMBERS = self::THREAD.'/thread-members';
+ // GET, PUT, DELETE
+ public const THREAD_MEMBER = self::THREAD_MEMBERS.'/:user_id';
+ // PUT, DELETE
+ public const THREAD_MEMBER_ME = self::THREAD_MEMBERS.'/@me';
+
+ // GET, DELETE
+ public const MESSAGE_REACTION_ALL = self::CHANNEL.'/messages/:message_id/reactions';
+ // GET, DELETE
+ public const MESSAGE_REACTION_EMOJI = self::CHANNEL.'/messages/:message_id/reactions/:emoji';
+ // PUT, DELETE
+ public const OWN_MESSAGE_REACTION = self::CHANNEL.'/messages/:message_id/reactions/:emoji/@me';
+ // DELETE
+ public const USER_MESSAGE_REACTION = self::CHANNEL.'/messages/:message_id/reactions/:emoji/:user_id';
+
+ // GET
+ protected const MESSAGE_POLL = self::CHANNEL.'/polls/:message_id';
+ // GET
+ public const MESSAGE_POLL_ANSWER = self::MESSAGE_POLL.'/answers/:answer_id';
+ // POST
+ public const MESSAGE_POLL_EXPIRE = self::MESSAGE_POLL.'/expire';
+
+ // GET, POST
+ public const CHANNEL_WEBHOOKS = self::CHANNEL.'/webhooks';
+
+ // POST
+ public const GUILDS = 'guilds';
+ // GET, PATCH, DELETE
+ public const GUILD = 'guilds/:guild_id';
+ // GET, POST, PATCH
+ public const GUILD_CHANNELS = self::GUILD.'/channels';
+ // GET
+ public const GUILD_THREADS_ACTIVE = self::GUILD.'/threads/active';
+ // GET
+ public const GUILD_MESSAGES_SEARCH = self::GUILD.'/messages/search';
+
+ // GET
+ public const GUILD_MEMBERS = self::GUILD.'/members';
+ // GET
+ public const GUILD_MEMBERS_SEARCH = self::GUILD.'/members/search';
+ // GET, PATCH, PUT, DELETE
+ public const GUILD_MEMBER = self::GUILD.'/members/:user_id';
+ // PATCH
+ public const GUILD_MEMBER_SELF = self::GUILD.'/members/@me';
+ /** @deprecated 9.0.9 Use `GUILD_MEMBER_SELF` */
+ public const GUILD_MEMBER_SELF_NICK = self::GUILD.'/members/@me/nick';
+ // PUT, DELETE
+ public const GUILD_MEMBER_ROLE = self::GUILD.'/members/:user_id/roles/:role_id';
+
+ // GET
+ public const GUILD_BANS = self::GUILD.'/bans';
+ // GET, PUT, DELETE
+ public const GUILD_BAN = self::GUILD.'/bans/:user_id';
+ // POST
+ public const GUILD_BAN_BULK = self::GUILD.'/bulk-ban';
+
+ // GET, PATCH
+ public const GUILD_ROLES = self::GUILD.'/roles';
+ // GET
+ public const GUILD_ROLES_MEMBER_COUNTS = self::GUILD.'/roles/member-counts';
+ // GET, POST, PATCH, DELETE
+ public const GUILD_ROLE = self::GUILD.'/roles/:role_id';
+
+ // POST
+ public const GUILD_MFA = self::GUILD.'/mfa';
+
+ // GET, POST
+ public const GUILD_INVITES = self::GUILD.'/invites';
+
+ // GET, POST
+ public const GUILD_INTEGRATIONS = self::GUILD.'/integrations';
+ // PATCH, DELETE
+ public const GUILD_INTEGRATION = self::GUILD.'/integrations/:integration_id';
+ // POST
+ public const GUILD_INTEGRATION_SYNC = self::GUILD.'/integrations/:integration_id/sync';
+
+ // GET, POST
+ public const GUILD_EMOJIS = self::GUILD.'/emojis';
+ // GET, PATCH, DELETE
+ public const GUILD_EMOJI = self::GUILD.'/emojis/:emoji_id';
+
+ // GET
+ public const GUILD_PREVIEW = self::GUILD.'/preview';
+ // GET, POST
+ public const GUILD_PRUNE = self::GUILD.'/prune';
+ // GET
+ public const GUILD_REGIONS = self::GUILD.'/regions';
+ // GET, PATCH
+ public const GUILD_WIDGET_SETTINGS = self::GUILD.'/widget';
+ // GET
+ public const GUILD_WIDGET = self::GUILD.'/widget.json';
+ // GET
+ public const GUILD_WIDGET_IMAGE = self::GUILD.'/widget.png';
+ // GET, PATCH
+ public const GUILD_WELCOME_SCREEN = self::GUILD.'/welcome-screen';
+ // GET
+ public const GUILD_ONBOARDING = self::GUILD.'/onboarding';
+ // GET
+ public const LIST_VOICE_REGIONS = 'voice/regions';
+ // GET, PATCH
+ public const GUILD_USER_CURRENT_VOICE_STATE = self::GUILD.'/voice-states/@me';
+ // GET, PATCH
+ public const GUILD_USER_VOICE_STATE = self::GUILD.'/voice-states/:user_id';
+ // GET
+ public const GUILD_VANITY_URL = self::GUILD.'/vanity-url';
+ // GET, PATCH
+ public const GUILD_MEMBERSHIP_SCREENING = self::GUILD.'/member-verification';
+ // GET
+ public const GUILD_WEBHOOKS = self::GUILD.'/webhooks';
+
+ // GET, POST
+ public const GUILD_STICKERS = self::GUILD.'/stickers';
+ // GET, PATCH, DELETE
+ public const GUILD_STICKER = self::GUILD.'/stickers/:sticker_id';
+
+ // GET
+ public const STICKER = 'stickers/:sticker_id';
+ // GET
+ public const STICKER_PACKS = 'sticker-packs';
+
+ // GET, POST
+ public const GUILD_SCHEDULED_EVENTS = self::GUILD.'/scheduled-events';
+ // GET, PATCH, DELETE
+ public const GUILD_SCHEDULED_EVENT = self::GUILD_SCHEDULED_EVENTS.'/:guild_scheduled_event_id';
+ // GET
+ public const GUILD_SCHEDULED_EVENT_USERS = self::GUILD_SCHEDULED_EVENT.'/users';
+ // GET
+ public const GUILD_SCHEDULED_EVENT_USERS_COUNT = self::GUILD_SCHEDULED_EVENT_USERS.'/counts';
+ // GET, POST
+ public const GUILD_SCHEDULED_EVENT_EXCEPTIONS = self::GUILD_SCHEDULED_EVENT.'/exceptions';
+ // PATCH, DELETE
+ public const GUILD_SCHEDULED_EVENT_EXCEPTION = self::GUILD_SCHEDULED_EVENT_EXCEPTIONS.'/:event_exception_id';
+ // GET
+ public const GUILD_SCHEDULED_EVENT_EXCEPTION_USERS = self::GUILD_SCHEDULED_EVENT_EXCEPTION.'/users';
+
+ // GET, POST
+ public const GUILD_SOUNDBOARD_SOUNDS = self::GUILD.'/soundboard-sounds';
+ // GET, PATCH, DELETE
+ public const GUILD_SOUNDBOARD_SOUND = self::GUILD.'/soundboard-sounds/:sound_id';
+
+ // GET, DELETE
+ public const INVITE = 'invites/:code';
+ // GET, PUT
+ public const INVITE_TARGET_USERS = self::INVITE.'/target-users';
+ // GET
+ public const INVITE_TARGET_USERS_JOB_STATUS = self::INVITE_TARGET_USERS.'/job-status';
+
+ // POST
+ public const STAGE_INSTANCES = 'stage-instances';
+ // GET, PATCH, DELETE
+ public const STAGE_INSTANCE = 'stage-instances/:channel_id';
+
+ // GET, POST
+ public const GUILDS_TEMPLATE = self::GUILDS.'/templates/:template_code';
+ // GET, POST
+ public const GUILD_TEMPLATES = self::GUILD.'/templates';
+ // PUT, PATCH, DELETE
+ public const GUILD_TEMPLATE = self::GUILD.'/templates/:template_code';
+
+ // GET, POST
+ public const GUILD_AUTO_MODERATION_RULES = self::GUILD.'/auto-moderation/rules';
+ // GET, PATCH, DELETE
+ public const GUILD_AUTO_MODERATION_RULE = self::GUILD.'/auto-moderation/rules/:auto_moderation_rule_id';
+
+ // POST
+ public const LOBBIES = 'lobbies';
+ // GET, PATCH, DELETE
+ public const LOBBY = self::LOBBIES.'/:lobby_id';
+ // PUT, DELETE
+ public const LOBBY_MEMBER = self::LOBBY.'/members/:user_id/';
+ // DELETE
+ public const LOBBY_SELF = self::LOBBY.'/members/@me';
+ // PATCH
+ public const LOBBY_CHANNEL_LINKING = self::LOBBY.'/channel-linking';
+
+ // GET
+ public const SOUNDBOARD_DEFAULT_SOUNDS = 'soundboard-default-sounds';
+
+ // GET, PATCH
+ public const USER_CURRENT = 'users/@me';
+ // GET
+ public const USER = 'users/:user_id';
+ // GET
+ public const USER_CURRENT_GUILDS = self::USER_CURRENT.'/guilds';
+ // DELETE
+ public const USER_CURRENT_GUILD = self::USER_CURRENT.'/guilds/:guild_id';
+ // GET
+ public const USER_CURRENT_MEMBER = self::USER_CURRENT_GUILD.'/member';
+ // GET, POST
+ public const USER_CURRENT_CHANNELS = self::USER_CURRENT.'/channels';
+ // GET
+ public const USER_CURRENT_CONNECTIONS = self::USER_CURRENT.'/connections';
+ // GET, PUT
+ public const USER_CURRENT_APPLICATION_ROLE_CONNECTION = self::USER_CURRENT.'/applications/:application_id/role-connection';
+ // GET, PATCH
+ public const APPLICATION_CURRENT = 'applications/@me';
+ // GET
+ public const APPLICATION_ACTIVITY_INSTANCE = 'applications/:application_id/activity-instances/:instance_id';
+
+ // GET, PATCH, DELETE
+ public const WEBHOOK = 'webhooks/:webhook_id';
+ // GET, PATCH, DELETE
+ public const WEBHOOK_TOKEN = 'webhooks/:webhook_id/:webhook_token';
+ // POST
+ public const WEBHOOK_EXECUTE = self::WEBHOOK_TOKEN;
+ // POST
+ public const WEBHOOK_EXECUTE_SLACK = self::WEBHOOK_EXECUTE.'/slack';
+ // POST
+ public const WEBHOOK_EXECUTE_GITHUB = self::WEBHOOK_EXECUTE.'/github';
+ // PATCH, DELETE
+ public const WEBHOOK_MESSAGE = self::WEBHOOK_TOKEN.'/messages/:message_id';
+
+ // GET, PUT
+ public const APPLICATION_ROLE_CONNECTION_METADATA = 'applications/:application_id/role-connections/metadata';
+
+ /**
+ * Regex to identify parameters in endpoints.
+ *
+ * @var string
+ */
+ public const REGEX = '/:([^\/]*)/';
+
+ /**
+ * A list of parameters considered 'major' by Discord.
+ *
+ * @see https://discord.com/developers/docs/topics/rate-limits
+ * @var string[]
+ */
+ public const MAJOR_PARAMETERS = ['channel_id', 'guild_id', 'webhook_id', 'thread_id'];
+
+ /**
+ * The string version of the endpoint, including all parameters.
+ *
+ * @var string
+ */
+ protected $endpoint;
+
+ /**
+ * Array of placeholders to be replaced in the endpoint.
+ *
+ * @var string[]
+ */
+ protected $vars = [];
+
+ /**
+ * Array of arguments to substitute into the endpoint.
+ *
+ * @var string[]
+ */
+ protected $args = [];
+
+ /**
+ * Array of query data to be appended
+ * to the end of the endpoint with `http_build_query`.
+ *
+ * @var array
+ */
+ protected $query = [];
+
+ /**
+ * Creates an endpoint class.
+ *
+ * @param string $endpoint
+ */
+ public function __construct(string $endpoint)
+ {
+ $this->endpoint = $endpoint;
+
+ if (preg_match_all(self::REGEX, $endpoint, $vars)) {
+ $this->vars = $vars[1] ?? [];
+ }
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/EndpointInterface.php b/vendor/discord-php/http/src/Discord/EndpointInterface.php
new file mode 100644
index 0000000..bea7cdb
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/EndpointInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+interface EndpointInterface
+{
+ public function bindArgs(...$args): self;
+ public function bindAssoc(array $args): self;
+ public function addQuery(string $key, $value): void;
+ public function toAbsoluteEndpoint(bool $onlyMajorParameters = false): string;
+ public function __toString(): string;
+ public static function bind(string $endpoint, ...$args);
+}
diff --git a/vendor/discord-php/http/src/Discord/EndpointTrait.php b/vendor/discord-php/http/src/Discord/EndpointTrait.php
new file mode 100644
index 0000000..8c59502
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/EndpointTrait.php
@@ -0,0 +1,137 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+trait EndpointTrait
+{
+ /**
+ * Binds a list of arguments to the endpoint.
+ *
+ * @param string[] ...$args
+ * @return this
+ */
+ public function bindArgs(...$args): self
+ {
+ for ($i = 0; $i < count($this->vars) && $i < count($args); $i++) {
+ $this->args[$this->vars[$i]] = $args[$i];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Binds an associative array to the endpoint.
+ *
+ * @param string[] $args
+ * @return this
+ */
+ public function bindAssoc(array $args): self
+ {
+ $this->args = array_merge($this->args, $args);
+
+ return $this;
+ }
+
+ /**
+ * Adds a key-value query pair to the endpoint.
+ *
+ * @param string $key
+ * @param string|bool $value
+ */
+ public function addQuery(string $key, $value): void
+ {
+ if (! is_bool($value)) {
+ $value = is_array($value)
+ ? (implode(' ', $value))
+ : (string) $value;
+ }
+
+ $this->query[$key] = $value;
+ }
+
+ /**
+ * Converts the endpoint into the absolute endpoint with
+ * placeholders replaced.
+ *
+ * Passing a true boolean in will only replace the major parameters.
+ * Used for rate limit buckets.
+ *
+ * @param bool $onlyMajorParameters
+ * @return string
+ */
+ public function toAbsoluteEndpoint(bool $onlyMajorParameters = false): string
+ {
+ $endpoint = $this->endpoint;
+
+ // Process in order of longest to shortest variable name to prevent partial replacements (see #16).
+ $vars = $this->vars;
+ usort($vars, fn ($a, $b) => strlen($b) <=> strlen($a));
+
+ foreach ($vars as $var) {
+ if (
+ ! isset($this->args[$var]) ||
+ (
+ $onlyMajorParameters &&
+ (method_exists($this, 'isMajorParameter') ? ! $this->isMajorParameter($var) : false)
+ )
+ ) {
+ continue;
+ }
+
+ $endpoint = str_replace(":{$var}", $this->args[$var], $endpoint);
+ }
+
+ if (! $onlyMajorParameters && count($this->query) > 0) {
+ $endpoint .= '?'.http_build_query($this->query);
+ }
+
+ return $endpoint;
+ }
+
+ /**
+ * Converts the endpoint to a string.
+ * Alias of ->toAbsoluteEndpoint();.
+ *
+ * @return string
+ */
+ public function __toString(): string
+ {
+ return $this->toAbsoluteEndpoint();
+ }
+
+ /**
+ * Creates an endpoint class and binds arguments to
+ * the newly created instance.
+ *
+ * @param string $endpoint
+ * @param string[] $args
+ * @return Endpoint
+ */
+ public static function bind(string $endpoint, ...$args)
+ {
+ $endpoint = new Endpoint($endpoint);
+ $endpoint->bindArgs(...$args);
+
+ return $endpoint;
+ }
+
+ /**
+ * Checks if a parameter is a major parameter.
+ *
+ * @param string $param
+ * @return bool
+ */
+ private static function isMajorParameter(string $param): bool
+ {
+ return in_array($param, Endpoint::MAJOR_PARAMETERS);
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/BadRequestException.php b/vendor/discord-php/http/src/Discord/Exceptions/BadRequestException.php
new file mode 100644
index 0000000..fdd2d22
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/BadRequestException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when a request to Discord's REST API returned ClientErrorResponse.
+ *
+ * @author SQKo
+ */
+class BadRequestException extends RequestFailedException
+{
+ protected $code = 400;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/ContentTooLongException.php b/vendor/discord-php/http/src/Discord/Exceptions/ContentTooLongException.php
new file mode 100644
index 0000000..e163293
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/ContentTooLongException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when the Discord servers return `content longer than 2000 characters` after
+ * a REST request. The user must use WebSockets to obtain this data if they need it.
+ *
+ * @author David Cole
+ */
+class ContentTooLongException extends RequestFailedException
+{
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/InvalidTokenException.php b/vendor/discord-php/http/src/Discord/Exceptions/InvalidTokenException.php
new file mode 100644
index 0000000..5152a24
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/InvalidTokenException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when an invalid token is provided to a Discord endpoint.
+ *
+ * @author David Cole
+ */
+class InvalidTokenException extends RequestFailedException
+{
+ protected $code = 401;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/MethodNotAllowedException.php b/vendor/discord-php/http/src/Discord/Exceptions/MethodNotAllowedException.php
new file mode 100644
index 0000000..899893e
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/MethodNotAllowedException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when a request to Discord's REST API method is invalid.
+ *
+ * @author SQKo
+ */
+class MethodNotAllowedException extends RequestFailedException
+{
+ protected $code = 405;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/NoPermissionsException.php b/vendor/discord-php/http/src/Discord/Exceptions/NoPermissionsException.php
new file mode 100644
index 0000000..2aa17b3
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/NoPermissionsException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when you do not have permissions to do something.
+ *
+ * @author David Cole
+ */
+class NoPermissionsException extends RequestFailedException
+{
+ protected $code = 403;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/NotFoundException.php b/vendor/discord-php/http/src/Discord/Exceptions/NotFoundException.php
new file mode 100644
index 0000000..dd4442e
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/NotFoundException.php
@@ -0,0 +1,22 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when a 404 Not Found response is received.
+ *
+ * @author David Cole
+ */
+class NotFoundException extends RequestFailedException
+{
+ protected $code = 404;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/RateLimitException.php b/vendor/discord-php/http/src/Discord/Exceptions/RateLimitException.php
new file mode 100644
index 0000000..711b009
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/RateLimitException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+/**
+ * Thrown when a request to Discord's REST API got rate limited and the library
+ * does not know how to handle.
+ *
+ * @author SQKo
+ */
+class RateLimitException extends RequestFailedException
+{
+ protected $code = 429;
+}
diff --git a/vendor/discord-php/http/src/Discord/Exceptions/RequestFailedException.php b/vendor/discord-php/http/src/Discord/Exceptions/RequestFailedException.php
new file mode 100644
index 0000000..56fb443
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Exceptions/RequestFailedException.php
@@ -0,0 +1,23 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Exceptions;
+
+use RuntimeException;
+
+/**
+ * Thrown when a request to Discord's REST API fails.
+ *
+ * @author David Cole
+ */
+class RequestFailedException extends RuntimeException
+{
+}
diff --git a/vendor/discord-php/http/src/Discord/Http.php b/vendor/discord-php/http/src/Discord/Http.php
new file mode 100644
index 0000000..ff004db
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Http.php
@@ -0,0 +1,152 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use Composer\InstalledVersions;
+use Psr\Log\LoggerInterface;
+use React\EventLoop\LoopInterface;
+use SplQueue;
+
+/**
+ * Discord HTTP client.
+ *
+ * @author David Cole
+ */
+class Http implements HttpInterface
+{
+ use HttpTrait;
+
+ /**
+ * DiscordPHP-Http version.
+ *
+ * @var string
+ */
+ public const VERSION = 'v10.8.0';
+
+ /**
+ * Current Discord HTTP API version.
+ *
+ * @var string
+ */
+ public const HTTP_API_VERSION = 10;
+
+ /**
+ * Discord API base URL.
+ *
+ * @var string
+ */
+ public const BASE_URL = 'https://discord.com/api/v'.self::HTTP_API_VERSION;
+
+ /**
+ * The number of concurrent requests which can
+ * be executed.
+ *
+ * @var int
+ */
+ public const CONCURRENT_REQUESTS = 5;
+
+ /**
+ * Authentication token.
+ *
+ * @var string
+ */
+ private $token;
+
+ /**
+ * Logger for HTTP requests.
+ *
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * HTTP driver.
+ *
+ * @var DriverInterface
+ */
+ protected $driver;
+
+ /**
+ * ReactPHP event loop.
+ *
+ * @var LoopInterface
+ */
+ protected $loop;
+
+ /**
+ * Array of request buckets.
+ *
+ * @var Bucket[]
+ */
+ protected $buckets = [];
+
+ /**
+ * The current rate-limit.
+ *
+ * @var RateLimit
+ */
+ protected $rateLimit;
+
+ /**
+ * Timer that resets the current global rate-limit.
+ *
+ * @var TimerInterface
+ */
+ protected $rateLimitReset;
+
+ /**
+ * Request queue to prevent API
+ * overload.
+ *
+ * @var SplQueue
+ */
+ protected $queue;
+
+ /**
+ * Request queue to prevent API
+ * overload.
+ *
+ * @var SplQueue
+ */
+ protected $unboundQueue;
+
+ /**
+ * Number of requests that are waiting for a response.
+ *
+ * @var int
+ */
+ protected $waiting = 0;
+
+ /**
+ * Whether react/promise v3 is used, if false, using v2.
+ */
+ protected $promiseV3 = true;
+
+ /**
+ * Http wrapper constructor.
+ *
+ * @param string $token
+ * @param LoopInterface $loop
+ * @param DriverInterface|null $driver
+ */
+ public function __construct(string $token, LoopInterface $loop, LoggerInterface $logger, ?DriverInterface $driver = null)
+ {
+ $this->token = $token;
+ $this->loop = $loop;
+ $this->logger = $logger;
+ $this->driver = $driver;
+ $this->queue = new SplQueue;
+ $this->unboundQueue = new SplQueue;
+
+ $this->promiseV3 = str_starts_with(InstalledVersions::getVersion('react/promise'), '3.');
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/HttpInterface.php b/vendor/discord-php/http/src/Discord/HttpInterface.php
new file mode 100644
index 0000000..b803ff1
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/HttpInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use Psr\Http\Message\ResponseInterface;
+use React\Promise\PromiseInterface;
+
+/**
+ * Discord HTTP client.
+ *
+ * @author David Cole
+ */
+interface HttpInterface
+{
+ public function setDriver(DriverInterface $driver): void;
+ public function get($url, $content = null, array $headers = []): PromiseInterface;
+ public function post($url, $content = null, array $headers = []): PromiseInterface;
+ public function put($url, $content = null, array $headers = []): PromiseInterface;
+ public function patch($url, $content = null, array $headers = []): PromiseInterface;
+ public function delete($url, $content = null, array $headers = []): PromiseInterface;
+ public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): PromiseInterface;
+ public static function isUnboundEndpoint(Request $request): bool;
+ public function handleError(ResponseInterface $response): \Throwable;
+ public function getUserAgent(): string;
+}
diff --git a/vendor/discord-php/http/src/Discord/HttpTrait.php b/vendor/discord-php/http/src/Discord/HttpTrait.php
new file mode 100644
index 0000000..8f4020d
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/HttpTrait.php
@@ -0,0 +1,480 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use Discord\Http\Exceptions\BadRequestException;
+use Discord\Http\Exceptions\ContentTooLongException;
+use Discord\Http\Exceptions\InvalidTokenException;
+use Discord\Http\Exceptions\MethodNotAllowedException;
+use Discord\Http\Exceptions\NoPermissionsException;
+use Discord\Http\Exceptions\NotFoundException;
+use Discord\Http\Exceptions\RateLimitException;
+use Discord\Http\Exceptions\RequestFailedException;
+use Discord\Http\Multipart\MultipartBody;
+use Psr\Http\Message\ResponseInterface;
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+
+/**
+ * Discord HTTP client.
+ *
+ * @author David Cole
+ */
+trait HttpTrait
+{
+ /**
+ * Sets the driver of the HTTP client.
+ *
+ * @param DriverInterface $driver
+ */
+ public function setDriver(DriverInterface $driver): void
+ {
+ $this->driver = $driver;
+ }
+
+ /**
+ * Runs a GET request.
+ *
+ * @param string|Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function get($url, $content = null, array $headers = []): PromiseInterface
+ {
+ if (! ($url instanceof Endpoint)) {
+ $url = Endpoint::bind($url);
+ }
+
+ return $this->queueRequest('get', $url, $content, $headers);
+ }
+
+ /**
+ * Runs a POST request.
+ *
+ * @param string|Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function post($url, $content = null, array $headers = []): PromiseInterface
+ {
+ if (! ($url instanceof Endpoint)) {
+ $url = Endpoint::bind($url);
+ }
+
+ return $this->queueRequest('post', $url, $content, $headers);
+ }
+
+ /**
+ * Runs a PUT request.
+ *
+ * @param string|Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function put($url, $content = null, array $headers = []): PromiseInterface
+ {
+ if (! ($url instanceof Endpoint)) {
+ $url = Endpoint::bind($url);
+ }
+
+ return $this->queueRequest('put', $url, $content, $headers);
+ }
+
+ /**
+ * Runs a PATCH request.
+ *
+ * @param string|Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function patch($url, $content = null, array $headers = []): PromiseInterface
+ {
+ if (! ($url instanceof Endpoint)) {
+ $url = Endpoint::bind($url);
+ }
+
+ return $this->queueRequest('patch', $url, $content, $headers);
+ }
+
+ /**
+ * Runs a DELETE request.
+ *
+ * @param string|Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function delete($url, $content = null, array $headers = []): PromiseInterface
+ {
+ if (! ($url instanceof Endpoint)) {
+ $url = Endpoint::bind($url);
+ }
+
+ return $this->queueRequest('delete', $url, $content, $headers);
+ }
+
+ /**
+ * Builds and queues a request.
+ *
+ * @param string $method
+ * @param Endpoint $url
+ * @param mixed $content
+ * @param array $headers
+ *
+ * @return PromiseInterface
+ */
+ public function queueRequest(string $method, Endpoint $url, $content, array $headers = []): PromiseInterface
+ {
+ $deferred = new Deferred();
+
+ if (is_null($this->driver)) {
+ $deferred->reject(new \Exception('HTTP driver is missing.'));
+
+ return $deferred->promise();
+ }
+
+ $headers = array_merge($headers, [
+ 'User-Agent' => $this->getUserAgent(),
+ 'Authorization' => $this->token,
+ 'X-Ratelimit-Precision' => 'millisecond',
+ ]);
+
+ $baseHeaders = [
+ 'User-Agent' => $this->getUserAgent(),
+ 'Authorization' => $this->token,
+ 'X-Ratelimit-Precision' => 'millisecond',
+ ];
+
+ if (! is_null($content) && ! isset($headers['Content-Type'])) {
+ $baseHeaders = array_merge(
+ $baseHeaders,
+ $this->guessContent($content)
+ );
+ }
+
+ $headers = array_merge($baseHeaders, $headers);
+
+ $request = new Request($deferred, $method, $url, $content ?? '', $headers);
+ $this->sortIntoBucket($request);
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Guesses the headers and transforms the content of a request.
+ *
+ * @param mixed $content
+ */
+ protected function guessContent(&$content)
+ {
+ if ($content instanceof MultipartBody) {
+ $headers = $content->getHeaders();
+ $content = (string) $content;
+
+ return $headers;
+ }
+
+ $content = json_encode($content);
+
+ return [
+ 'Content-Type' => 'application/json',
+ 'Content-Length' => strlen($content),
+ ];
+ }
+
+ /**
+ * Executes a request.
+ *
+ * @param Request $request
+ * @param Deferred|null $deferred
+ *
+ * @return PromiseInterface
+ */
+ protected function executeRequest(Request $request, ?Deferred $deferred = null): PromiseInterface
+ {
+ if ($deferred === null) {
+ $deferred = new Deferred();
+ }
+
+ if ($this->rateLimit) {
+ $deferred->reject($this->rateLimit);
+
+ return $deferred->promise();
+ }
+
+ // Promises v3 changed `->then` to behave as `->done` and removed `->then`. We still need the behaviour of `->done` in projects using v2
+ $this->driver->runRequest($request)->{$this->promiseV3 ? 'then' : 'done'}(function (ResponseInterface $response) use ($request, $deferred) {
+ $data = json_decode((string) $response->getBody());
+ $statusCode = $response->getStatusCode();
+
+ // Discord Rate-limit
+ if ($statusCode == 429) {
+ if (! isset($data->global)) {
+ if ($response->hasHeader('X-RateLimit-Global')) {
+ $data->global = $response->getHeader('X-RateLimit-Global')[0] == 'true';
+ } else {
+ // Some other 429
+ $this->logger->error($request.' does not contain global rate-limit value');
+ $rateLimitError = new RateLimitException('No rate limit global response', $statusCode);
+ $deferred->reject($rateLimitError);
+ $request->getDeferred()->reject($rateLimitError);
+
+ return;
+ }
+ }
+
+ if (! isset($data->retry_after)) {
+ if ($response->hasHeader('Retry-After')) {
+ $data->retry_after = $response->getHeader('Retry-After')[0];
+ } else {
+ // Some other 429
+ $this->logger->error($request.' does not contain retry after rate-limit value');
+ $rateLimitError = new RateLimitException('No rate limit retry after response', $statusCode);
+ $deferred->reject($rateLimitError);
+ $request->getDeferred()->reject($rateLimitError);
+
+ return;
+ }
+ }
+
+ $rateLimit = new RateLimit($data->global, $data->retry_after);
+ $this->logger->warning($request.' hit rate-limit: '.$rateLimit);
+
+ if ($rateLimit->isGlobal() && ! $this->rateLimit) {
+ $this->rateLimit = $rateLimit;
+ $this->rateLimitReset = $this->loop->addTimer($rateLimit->getRetryAfter(), function () {
+ $this->rateLimit = null;
+ $this->rateLimitReset = null;
+ $this->logger->info('global rate-limit reset');
+
+ // Loop through all buckets and check for requests
+ foreach ($this->buckets as $bucket) {
+ $bucket->checkQueue();
+ }
+ });
+ }
+
+ $deferred->reject($rateLimit->isGlobal() ? $this->rateLimit : $rateLimit);
+ }
+ // Bad Gateway
+ // Cloudflare SSL Handshake error
+ // Push to the back of the bucket to be retried.
+ elseif ($statusCode == 502 || $statusCode == 525) {
+ $this->logger->warning($request.' 502/525 - retrying request');
+
+ $this->executeRequest($request, $deferred);
+ }
+ // Any other unsuccessful status codes
+ elseif ($statusCode < 200 || $statusCode >= 300) {
+ $error = $this->handleError($response);
+ $this->logger->warning($request.' failed: '.$error);
+
+ $deferred->reject($error);
+ $request->getDeferred()->reject($error);
+ }
+ // All is well
+ else {
+ $this->logger->debug($request.' successful');
+
+ $deferred->resolve($response);
+ $request->getDeferred()->resolve($data);
+ }
+ }, function (\Exception $e) use ($request, $deferred) {
+ $this->logger->warning($request.' failed: '.$e->getMessage());
+
+ $deferred->reject($e);
+ $request->getDeferred()->reject($e);
+ });
+
+ return $deferred->promise();
+ }
+
+ /**
+ * Sorts a request into a bucket.
+ *
+ * @param Request $request
+ */
+ protected function sortIntoBucket(Request $request): void
+ {
+ $bucket = $this->getBucket($request->getBucketID());
+ $bucket->enqueue($request);
+ }
+
+ /**
+ * Gets a bucket.
+ *
+ * @param string $key
+ *
+ * @return Bucket
+ */
+ protected function getBucket(string $key): Bucket
+ {
+ if (! isset($this->buckets[$key])) {
+ $bucket = new Bucket($key, $this->loop, $this->logger, function (Request $request) {
+ $deferred = new Deferred();
+ self::isUnboundEndpoint($request)
+ ? $this->unboundQueue->enqueue([$request, $deferred])
+ : $this->queue->enqueue([$request, $deferred]);
+ $this->checkQueue();
+
+ return $deferred->promise();
+ });
+
+ $this->buckets[$key] = $bucket;
+ }
+
+ return $this->buckets[$key];
+ }
+
+ /**
+ * Checks the request queue to see if more requests can be
+ * sent out.
+ */
+ protected function checkQueue(bool $check_interactions = true): void
+ {
+ if ($check_interactions) {
+ $this->checkunboundQueue();
+ }
+
+ if ($this->waiting >= Http::CONCURRENT_REQUESTS || $this->queue->isEmpty()) {
+ $this->logger->debug('http not checking queue', ['waiting' => $this->waiting, 'empty' => $this->queue->isEmpty()]);
+
+ return;
+ }
+
+ /**
+ * @var Request $request
+ * @var Deferred $deferred
+ */
+ [$request, $deferred] = $this->queue->dequeue();
+ ++$this->waiting;
+
+ $this->executeRequest($request)->then(function ($result) use ($deferred) {
+ --$this->waiting;
+ $this->checkQueue(false);
+ $deferred->resolve($result);
+ }, function ($e) use ($deferred) {
+ --$this->waiting;
+ $this->checkQueue(false);
+ $deferred->reject($e);
+ });
+ }
+
+ /**
+ * Checks the interaction queue to see if more requests can be
+ * sent out.
+ */
+ protected function checkunboundQueue(): void
+ {
+ if ($this->unboundQueue->isEmpty()) {
+ $this->logger->debug('http not checking interaction queue', ['waiting' => $this->waiting, 'empty' => $this->unboundQueue->isEmpty()]);
+
+ return;
+ }
+
+ /**
+ * @var Request $request
+ * @var Deferred $deferred
+ */
+ [$request, $deferred] = $this->unboundQueue->dequeue();
+
+ $this->executeRequest($request)->then(function ($result) use ($deferred) {
+ $this->checkQueue();
+ $deferred->resolve($result);
+ }, function ($e) use ($deferred) {
+ $this->checkQueue();
+ $deferred->reject($e);
+ });
+ }
+
+ /**
+ * Checks if the request is for an endpoint not bound by the global rate limit.
+ *
+ * @link https://discord.com/developers/docs/interactions/receiving-and-responding#endpoints
+ *
+ * @param Request $request
+ * @return bool
+ */
+ public static function isUnboundEndpoint(Request $request): bool
+ {
+ $url = $request->getUrl();
+
+ return
+ (strpos($url, '/interactions') === 0 && strpos($url, '/callback') !== false)
+ || strpos($url, '/webhooks') === 0;
+ }
+
+ /**
+ * Returns an exception based on the request.
+ *
+ * @param ResponseInterface $response
+ *
+ * @return \Throwable
+ */
+ public function handleError(ResponseInterface $response): \Throwable
+ {
+ $reason = $response->getReasonPhrase().' - ';
+
+ $errorBody = (string) $response->getBody();
+ $errorCode = $response->getStatusCode();
+
+ // attempt to prettyify the response content
+ if (($content = json_decode($errorBody)) !== null) {
+ if (! empty($content->code)) {
+ $errorCode = $content->code;
+ }
+ $reason .= json_encode($content, JSON_PRETTY_PRINT);
+ } else {
+ $reason .= $errorBody;
+ }
+
+ switch ($response->getStatusCode()) {
+ case 400:
+ return new BadRequestException($reason, $errorCode);
+ case 401:
+ return new InvalidTokenException($reason, $errorCode);
+ case 403:
+ return new NoPermissionsException($reason, $errorCode);
+ case 404:
+ return new NotFoundException($reason, $errorCode);
+ case 405:
+ return new MethodNotAllowedException($reason, $errorCode);
+ case 500:
+ if (strpos(strtolower($errorBody), 'longer than 2000 characters') !== false ||
+ strpos(strtolower($errorBody), 'string value is too long') !== false) {
+ // Response was longer than 2000 characters and was blocked by Discord.
+ return new ContentTooLongException('Response was more than 2000 characters. Use another method to get this data.', $errorCode);
+ }
+ default:
+ return new RequestFailedException($reason, $errorCode);
+ }
+ }
+
+ /**
+ * Returns the User-Agent of the HTTP client.
+ *
+ * @return string
+ */
+ public function getUserAgent(): string
+ {
+ return 'DiscordBot (https://github.com/discord-php/DiscordPHP-HTTP, '.Http::VERSION.')';
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Multipart/MultipartBody.php b/vendor/discord-php/http/src/Discord/Multipart/MultipartBody.php
new file mode 100644
index 0000000..db9a2cf
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Multipart/MultipartBody.php
@@ -0,0 +1,58 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Multipart;
+
+class MultipartBody
+{
+ public const BOUNDARY = 'DISCORDPHP-HTTP-BOUNDARY';
+
+ private array $fields;
+ public string $boundary;
+
+ /**
+ * @var MultipartField[]
+ */
+ public function __construct(array $fields, ?string $boundary = null)
+ {
+ $this->fields = $fields;
+ $this->boundary = $boundary ?? self::BOUNDARY;
+ }
+
+ public function __toString(): string
+ {
+ $prefixedBoundary = '--'.$this->boundary;
+ $boundaryEnd = $prefixedBoundary.'--';
+
+ $convertedFields = array_map(
+ function (MultipartField $field) {
+ return (string) $field;
+ },
+ $this->fields
+ );
+
+ $fieldsString = implode(PHP_EOL.$prefixedBoundary.PHP_EOL, $convertedFields);
+
+ return implode(PHP_EOL, [
+ $prefixedBoundary,
+ $fieldsString,
+ $boundaryEnd,
+ ]);
+ }
+
+ public function getHeaders(): array
+ {
+ return [
+ 'Content-Type' => 'multipart/form-data; boundary='.$this->boundary,
+ 'Content-Length' => strlen((string) $this),
+ ];
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Multipart/MultipartField.php b/vendor/discord-php/http/src/Discord/Multipart/MultipartField.php
new file mode 100644
index 0000000..2745706
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Multipart/MultipartField.php
@@ -0,0 +1,54 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http\Multipart;
+
+class MultipartField
+{
+ private string $name;
+ private string $content;
+ private array $headers;
+ private ?string $fileName;
+
+ /**
+ * @var String[]
+ */
+ public function __construct(
+ string $name,
+ string $content,
+ array $headers = [],
+ ?string $fileName = null
+ ) {
+ $this->name = $name;
+ $this->content = $content;
+ $this->headers = $headers;
+ $this->fileName = $fileName;
+ }
+
+ public function __toString(): string
+ {
+ $out = 'Content-Disposition: form-data; name="'.$this->name.'"';
+
+ if (! is_null($this->fileName)) {
+ $out .= '; filename="'.urlencode($this->fileName).'"';
+ }
+
+ $out .= PHP_EOL;
+
+ foreach ($this->headers as $header => $value) {
+ $out .= $header.': '.$value.PHP_EOL;
+ }
+
+ $out .= PHP_EOL.$this->content.PHP_EOL;
+
+ return $out;
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/RateLimit.php b/vendor/discord-php/http/src/Discord/RateLimit.php
new file mode 100644
index 0000000..0373f3f
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/RateLimit.php
@@ -0,0 +1,78 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use RuntimeException;
+
+/**
+ * Represents a rate-limit given by Discord.
+ *
+ * @author David Cole
+ */
+class RateLimit extends RuntimeException
+{
+ /**
+ * Whether the rate-limit is global.
+ *
+ * @var bool
+ */
+ protected $global;
+
+ /**
+ * Time in seconds of when to retry after.
+ *
+ * @var float
+ */
+ protected $retry_after;
+
+ /**
+ * Rate limit constructor.
+ *
+ * @param bool $global
+ * @param float $retry_after
+ */
+ public function __construct(bool $global, float $retry_after)
+ {
+ $this->global = $global;
+ $this->retry_after = $retry_after;
+ }
+
+ /**
+ * Gets the global parameter.
+ *
+ * @return bool
+ */
+ public function isGlobal(): bool
+ {
+ return $this->global;
+ }
+
+ /**
+ * Gets the retry after parameter.
+ *
+ * @return float
+ */
+ public function getRetryAfter(): float
+ {
+ return $this->retry_after;
+ }
+
+ /**
+ * Converts a rate-limit to a user-readable string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return 'RATELIMIT '.($this->global ? 'Global' : 'Non-global').', retry after '.$this->retry_after.' s';
+ }
+}
diff --git a/vendor/discord-php/http/src/Discord/Request.php b/vendor/discord-php/http/src/Discord/Request.php
new file mode 100644
index 0000000..190bad5
--- /dev/null
+++ b/vendor/discord-php/http/src/Discord/Request.php
@@ -0,0 +1,145 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Discord\Http;
+
+use React\Promise\Deferred;
+
+/**
+ * Represents an HTTP request.
+ *
+ * @author David Cole
+ */
+class Request
+{
+ /**
+ * Deferred promise.
+ *
+ * @var Deferred
+ */
+ protected $deferred;
+
+ /**
+ * Request method.
+ *
+ * @var string
+ */
+ protected $method;
+
+ /**
+ * Request URL.
+ *
+ * @var Endpoint
+ */
+ protected $url;
+
+ /**
+ * Request content.
+ *
+ * @var string
+ */
+ protected $content;
+
+ /**
+ * Request headers.
+ *
+ * @var array
+ */
+ protected $headers;
+
+ /**
+ * Request constructor.
+ *
+ * @param Deferred $deferred
+ * @param string $method
+ * @param Endpoint $url
+ * @param string $content
+ * @param array $headers
+ */
+ public function __construct(Deferred $deferred, string $method, Endpoint $url, string $content, array $headers = [])
+ {
+ $this->deferred = $deferred;
+ $this->method = $method;
+ $this->url = $url;
+ $this->content = $content;
+ $this->headers = $headers;
+ }
+
+ /**
+ * Gets the method.
+ *
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ /**
+ * Gets the url.
+ *
+ * @return string
+ */
+ public function getUrl(): string
+ {
+ return Http::BASE_URL.'/'.$this->url;
+ }
+
+ /**
+ * Gets the content.
+ *
+ * @return string
+ */
+ public function getContent(): string
+ {
+ return $this->content;
+ }
+
+ /**
+ * Gets the headers.
+ *
+ * @return string
+ */
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+
+ /**
+ * Returns the deferred promise.
+ *
+ * @return Deferred
+ */
+ public function getDeferred(): Deferred
+ {
+ return $this->deferred;
+ }
+
+ /**
+ * Returns the bucket ID for the request.
+ *
+ * @return string
+ */
+ public function getBucketID(): string
+ {
+ return $this->method.$this->url->toAbsoluteEndpoint(true);
+ }
+
+ /**
+ * Converts the request to a user-readable string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return 'REQ '.strtoupper($this->method).' '.$this->url;
+ }
+}
diff --git a/vendor/discord-php/http/tests/Discord/DriverInterfaceTest.php b/vendor/discord-php/http/tests/Discord/DriverInterfaceTest.php
new file mode 100644
index 0000000..4f833b1
--- /dev/null
+++ b/vendor/discord-php/http/tests/Discord/DriverInterfaceTest.php
@@ -0,0 +1,144 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http;
+
+use Discord\Http\DriverInterface;
+use Discord\Http\Request;
+use Mockery;
+use PHPUnit\Framework\TestCase;
+use Psr\Http\Message\ResponseInterface;
+use function React\Async\await;
+
+abstract class DriverInterfaceTest extends TestCase
+{
+ abstract protected function getDriver(): DriverInterface;
+
+ private function getRequest(
+ string $method,
+ string $url,
+ string $content = '',
+ array $headers = []
+ ): Request {
+ $request = Mockery::mock(Request::class);
+
+ $request->shouldReceive([
+ 'getMethod' => $method,
+ 'getUrl' => $url,
+ 'getContent' => $content,
+ 'getHeaders' => $headers,
+ ]);
+
+ return $request;
+ }
+
+ /**
+ * @dataProvider requestProvider
+ */
+ public function testRequest(string $method, string $url, array $content = [], array $verify = [])
+ {
+ $driver = $this->getDriver();
+ $request = $this->getRequest(
+ $method,
+ $url,
+ $content === [] ? '' : json_encode($content),
+ empty($content) ? [] : ['Content-Type' => 'application/json']
+ );
+
+ /** @var ResponseInterface */
+ $response = await($driver->runRequest($request));
+
+ $this->assertNotEquals('', $response->getBody());
+ $this->assertEquals(200, $response->getStatusCode());
+
+ $jsonDecodedBody = json_decode($response->getBody(), true);
+
+ $verify['method'] = $method;
+
+ foreach ($verify as $field => $expectedValue) {
+ $this->assertEquals(
+ $expectedValue,
+ $jsonDecodedBody[$field]
+ );
+ }
+ }
+
+ public function requestProvider(): array
+ {
+ $content = ['something' => 'value'];
+
+ return [
+ 'Plain get' => [
+ 'method' => 'GET',
+ 'url' => 'http://127.0.0.1:8888',
+ ],
+ 'Get with params' => [
+ 'method' => 'GET',
+ 'url' => 'http://127.0.0.1:8888?something=value',
+ 'verify' => [
+ 'args' => $content,
+ ],
+ ],
+
+ 'Plain post' => [
+ 'method' => 'POST',
+ 'url' => 'http://127.0.0.1:8888',
+ ],
+ 'Post with content' => [
+ 'method' => 'POST',
+ 'url' => 'http://127.0.0.1:8888',
+ 'content' => $content,
+ 'verify' => [
+ 'json' => $content,
+ ],
+ ],
+
+ 'Plain put' => [
+ 'method' => 'PUT',
+ 'url' => 'http://127.0.0.1:8888',
+ ],
+ 'Put with content' => [
+ 'method' => 'PUT',
+ 'url' => 'http://127.0.0.1:8888',
+ 'content' => $content,
+ 'verify' => [
+ 'json' => $content,
+ ],
+ ],
+
+ 'Plain patch' => [
+ 'method' => 'PATCH',
+ 'url' => 'http://127.0.0.1:8888',
+ ],
+ 'Patch with content' => [
+ 'method' => 'PATCH',
+ 'url' => 'http://127.0.0.1:8888',
+ 'content' => $content,
+ 'verify' => [
+ 'json' => $content,
+ ],
+ ],
+
+ 'Plain delete' => [
+ 'method' => 'DELETE',
+ 'url' => 'http://127.0.0.1:8888',
+ ],
+ 'Delete with content' => [
+ 'method' => 'DELETE',
+ 'url' => 'http://127.0.0.1:8888',
+ 'content' => $content,
+ 'verify' => [
+ 'json' => $content,
+ ],
+ ],
+ ];
+ }
+}
diff --git a/vendor/discord-php/http/tests/Discord/EndpointTest.php b/vendor/discord-php/http/tests/Discord/EndpointTest.php
new file mode 100644
index 0000000..8bb38a6
--- /dev/null
+++ b/vendor/discord-php/http/tests/Discord/EndpointTest.php
@@ -0,0 +1,174 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http;
+
+use Discord\Http\Endpoint;
+use PHPUnit\Framework\TestCase;
+
+class EndpointTest extends TestCase
+{
+ /**
+ * @dataProvider majorParamProvider
+ */
+ public function testBindMajorParams(string $uri, array $replacements, string $expected)
+ {
+ $endpoint = new Endpoint($uri);
+ $endpoint->bindArgs(...$replacements);
+
+ $this->assertEquals(
+ $endpoint->toAbsoluteEndpoint(true),
+ $expected
+ );
+ }
+
+ public function majorParamProvider(): array
+ {
+ return [
+ 'Several major params' => [
+ 'uri' => 'something/:guild_id/:channel_id/:webhook_id',
+ 'replacements' => ['::guild id::', '::channel id::', '::webhook id::'],
+ 'expected' => 'something/::guild id::/::channel id::/::webhook id::',
+ ],
+ 'Single major param' => [
+ 'uri' => 'something/:guild_id',
+ 'replacements' => ['::guild id::'],
+ 'expected' => 'something/::guild id::',
+ ],
+ 'Single major param, some minor params' => [
+ 'uri' => 'something/:guild_id/:some_param/:something_else',
+ 'replacements' => ['::guild id::', '::some_param::', '::something else::'],
+ 'expected' => 'something/::guild id::/:some_param/:something_else',
+ ],
+ 'Only minor params' => [
+ 'uri' => 'something/:something/:some_param/:something_else',
+ 'replacements' => ['::something::', '::some_param::', '::something else::'],
+ 'expected' => 'something/:something/:some_param/:something_else',
+ ],
+ 'Minor and major params in weird order' => [
+ 'uri' => 'something/:something/:guild_id/:something_else/:channel_id',
+ 'replacements' => ['::something::', '::guild id::', '::something else::', '::channel id::'],
+ 'expected' => 'something/:something/::guild id::/:something_else/::channel id::',
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider allParamProvider
+ */
+ public function testBindAllParams(string $uri, array $replacements, string $expected)
+ {
+ $endpoint = new Endpoint($uri);
+ $endpoint->bindArgs(...$replacements);
+
+ $this->assertEquals(
+ $expected,
+ $endpoint->toAbsoluteEndpoint()
+ );
+ }
+
+ public function allParamProvider(): array
+ {
+ return [
+ 'Several major params' => [
+ 'uri' => 'something/:guild_id/:channel_id/:webhook_id',
+ 'replacements' => ['::guild id::', '::channel id::', '::webhook id::'],
+ 'expected' => 'something/::guild id::/::channel id::/::webhook id::',
+ ],
+ 'Single major param' => [
+ 'uri' => 'something/:guild_id',
+ 'replacements' => ['::guild id::'],
+ 'expected' => 'something/::guild id::',
+ ],
+ 'Single major param, some minor params' => [
+ 'uri' => 'something/:guild_id/:some_param/:something_else',
+ 'replacements' => ['::guild id::', '::some param::', '::something else::'],
+ 'expected' => 'something/::guild id::/::some param::/::something else::',
+ ],
+ 'Only minor params' => [
+ 'uri' => 'something/:something/:some_param/:other',
+ 'replacements' => ['::something::', '::some param::', '::something else::'],
+ 'expected' => 'something/::something::/::some param::/::something else::',
+ ],
+ 'Minor and major params in weird order' => [
+ 'uri' => 'something/:something/:guild_id/:other/:channel_id',
+ 'replacements' => ['::something::', '::guild id::', '::something else::', '::channel id::'],
+ 'expected' => 'something/::something::/::guild id::/::something else::/::channel id::',
+ ],
+
+ // @see https://github.com/discord-php/DiscordPHP-Http/issues/16
+ // 'Params with same prefix, short first' => [
+ // 'uri' => 'something/:thing/:thing_other',
+ // 'replacements' => ['::thing::', '::thing other::'],
+ // 'expected' => 'something/::thing::/::thing other::',
+ // ],
+ // 'Params with same prefix, short first' => [
+ // 'uri' => 'something/:thing_other/:thing',
+ // 'replacements' => ['::thing other::', '::thing::'],
+ // 'expected' => 'something/::thing other::/::thing::',
+ // ],
+ ];
+ }
+
+ public function testBindAssoc()
+ {
+ $endpoint = new Endpoint('something/:first/:second');
+ $endpoint->bindAssoc([
+ 'second' => '::second::',
+ 'first' => '::first::',
+ ]);
+
+ $this->assertEquals(
+ 'something/::first::/::second::',
+ $endpoint->toAbsoluteEndpoint()
+ );
+ }
+
+ public function testItConvertsToString()
+ {
+ $this->assertEquals(
+ 'something/::first::/::second::',
+ (string) Endpoint::bind(
+ 'something/:first/:second',
+ '::first::',
+ '::second::'
+ )
+ );
+ }
+
+ public function itCanAddQueryParams()
+ {
+ $endpoint = new Endpoint('something/:param');
+ $endpoint->bindArgs('param');
+
+ $endpoint->addQuery('something', 'value');
+ $endpoint->addQuery('boolval', true);
+
+ $this->assertEquals(
+ 'something/param?something=value&boolval=1',
+ $endpoint->toAbsoluteEndpoint()
+ );
+ }
+
+ public function itDoesNotAddQueryParamsForMajorParameters()
+ {
+ $endpoint = new Endpoint('something/:guild_id');
+ $endpoint->bindArgs('param');
+
+ $endpoint->addQuery('something', 'value');
+ $endpoint->addQuery('boolval', true);
+
+ $this->assertEquals(
+ 'something/param',
+ $endpoint->toAbsoluteEndpoint(true)
+ );
+ }
+}
diff --git a/vendor/discord-php/http/tests/Discord/Multipart/MultipartTest.php b/vendor/discord-php/http/tests/Discord/Multipart/MultipartTest.php
new file mode 100644
index 0000000..d867016
--- /dev/null
+++ b/vendor/discord-php/http/tests/Discord/Multipart/MultipartTest.php
@@ -0,0 +1,126 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http\Multipart;
+
+use Discord\Http\Multipart\MultipartBody;
+use Discord\Http\Multipart\MultipartField;
+use Mockery;
+use PHPUnit\Framework\TestCase;
+
+class MultipartTest extends TestCase
+{
+ /**
+ * @dataProvider multipartFieldStringConversionProvider
+ */
+ public function testMultipartFieldStringConversion(array $constructorArgs, string $expected)
+ {
+ $multipartField = new MultipartField(...$constructorArgs);
+
+ $this->assertEquals($expected, (string) $multipartField);
+ }
+
+ public function multipartFieldStringConversionProvider(): array
+ {
+ return [
+ 'Completely filled' => [
+ 'args' => [
+ '::name::',
+ '::content::',
+ [
+ 'Header-Name' => 'Value',
+ ],
+ '::filename::',
+ ],
+
+ 'expected' => << [
+ 'args' => [
+ '::name::',
+ '::content::',
+ [
+ 'Header-Name' => 'Value',
+ ],
+ null,
+ ],
+
+ 'expected' => << [
+ 'args' => [
+ '::name::',
+ '::content::',
+ [],
+ '::filename::',
+ ],
+
+ 'expected' => <<shouldReceive('__toString')->andReturn($return);
+
+ return $mock;
+ }, ['::first field::', '::second field::', '::third field::']);
+
+ $multipartBody = new MultipartBody($fields, '::boundary::');
+
+ $this->assertEquals(
+ <<assertEquals([
+ 'Content-Type' => 'multipart/form-data; boundary=::boundary::',
+ 'Content-Length' => strlen((string) $multipartBody),
+ ], $multipartBody->getHeaders());
+ }
+
+ public function testGeneratingBoundary()
+ {
+ $multipartBody = new MultipartBody([
+ Mockery::mock(MultipartField::class),
+ ]);
+
+ $this->assertNotNull($multipartBody->boundary);
+ }
+}
diff --git a/vendor/discord-php/http/tests/Discord/RequestTest.php b/vendor/discord-php/http/tests/Discord/RequestTest.php
new file mode 100644
index 0000000..1c86e0c
--- /dev/null
+++ b/vendor/discord-php/http/tests/Discord/RequestTest.php
@@ -0,0 +1,87 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http;
+
+use Discord\Http\Endpoint;
+use Discord\Http\Http;
+use Discord\Http\Request;
+use Mockery;
+use PHPUnit\Framework\TestCase;
+use React\Promise\Deferred;
+
+class RequestTest extends TestCase
+{
+ private function getRequest(
+ ?Deferred $deferred = null,
+ string $method = '',
+ ?Endpoint $url = null,
+ string $content = '',
+ array $headers = []
+ ) {
+ $url = $url ?? new Endpoint('');
+ $deferred = $deferred ?? new Deferred();
+
+ return new Request(
+ $deferred,
+ $method,
+ $url,
+ $content,
+ $headers
+ );
+ }
+
+ public function testGetDeferred()
+ {
+ $deferred = Mockery::mock(Deferred::class);
+ $request = $this->getRequest($deferred);
+
+ $this->assertEquals($deferred, $request->getDeferred());
+ }
+
+ public function testGetMethod()
+ {
+ $request = $this->getRequest(null, '::method::');
+
+ $this->assertEquals('::method::', $request->getMethod());
+ }
+
+ public function testGetUrl()
+ {
+ $request = $this->getRequest(null, '', new Endpoint('::url::'));
+
+ $this->assertEquals(Http::BASE_URL.'/::url::', $request->getUrl());
+ }
+
+ public function testGetContent()
+ {
+ $request = $this->getRequest(null, '', null, '::content::');
+
+ $this->assertEquals('::content::', $request->getContent());
+ }
+
+ public function testGetHeaders()
+ {
+ $request = $this->getRequest(null, '', null, '::content::', ['something' => 'value']);
+
+ $this->assertEquals(['something' => 'value'], $request->getHeaders());
+ }
+
+ public function testGetBucketId()
+ {
+ $endpoint = Mockery::mock(Endpoint::class);
+ $endpoint->shouldReceive('toAbsoluteEndpoint')->andReturn('::endpoint::');
+
+ $request = $this->getRequest(null, '::method::', $endpoint);
+
+ $this->assertEquals('::method::::endpoint::', $request->getBucketID());
+ }
+}
diff --git a/vendor/discord-php/http/tests/Drivers/GuzzleTest.php b/vendor/discord-php/http/tests/Drivers/GuzzleTest.php
new file mode 100644
index 0000000..f6d7666
--- /dev/null
+++ b/vendor/discord-php/http/tests/Drivers/GuzzleTest.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http\Drivers;
+
+use Discord\Http\DriverInterface;
+use Discord\Http\Drivers\Guzzle;
+use React\EventLoop\Loop;
+use Tests\Discord\Http\DriverInterfaceTest;
+
+class GuzzleTest extends DriverInterfaceTest
+{
+ protected function getDriver(): DriverInterface
+ {
+ return new Guzzle(Loop::get());
+ }
+}
diff --git a/vendor/discord-php/http/tests/Drivers/ReactTest.php b/vendor/discord-php/http/tests/Drivers/ReactTest.php
new file mode 100644
index 0000000..6efab69
--- /dev/null
+++ b/vendor/discord-php/http/tests/Drivers/ReactTest.php
@@ -0,0 +1,25 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+namespace Tests\Discord\Http\Drivers;
+
+use Discord\Http\DriverInterface;
+use Discord\Http\Drivers\React;
+use React\EventLoop\Loop;
+use Tests\Discord\Http\DriverInterfaceTest;
+
+class ReactTest extends DriverInterfaceTest
+{
+ protected function getDriver(): DriverInterface
+ {
+ return new React(Loop::get());
+ }
+}
diff --git a/vendor/discord-php/http/tests/Drivers/_server.php b/vendor/discord-php/http/tests/Drivers/_server.php
new file mode 100644
index 0000000..6a5db2a
--- /dev/null
+++ b/vendor/discord-php/http/tests/Drivers/_server.php
@@ -0,0 +1,32 @@
+
+ *
+ * This file is subject to the MIT license that is bundled
+ * with this source code in the LICENSE file.
+ */
+
+use React\Http\HttpServer;
+use React\Http\Message\Response;
+use React\Socket\SocketServer;
+
+require __DIR__.'/../../vendor/autoload.php';
+
+$http = new HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
+ $response = [
+ 'method' => $request->getMethod(),
+ 'args' => $request->getQueryParams(),
+ 'json' => $request->getHeader('Content-Type') === ['application/json']
+ ? json_decode($request->getBody())
+ : [],
+ ];
+
+ return Response::json($response);
+});
+
+$socket = new SocketServer('127.0.0.1:8888');
+
+$http->listen($socket);
diff --git a/vendor/discord/interactions/.gitignore b/vendor/discord/interactions/.gitignore
new file mode 100644
index 0000000..7a8a0ad
--- /dev/null
+++ b/vendor/discord/interactions/.gitignore
@@ -0,0 +1,3 @@
+composer.phar
+composer.lock
+vendor
diff --git a/vendor/discord/interactions/README.md b/vendor/discord/interactions/README.md
new file mode 100644
index 0000000..5c39e96
--- /dev/null
+++ b/vendor/discord/interactions/README.md
@@ -0,0 +1,46 @@
+discord-interactions-php
+---
+
+Types and helper functions that may come in handy when you implement a Discord Interactions webhook.
+
+# Installation
+
+Install from [packagist](https://packagist.org/packages/discord/interactions):
+
+```
+composer require discord/interactions
+```
+
+Validating request signatures requires the [`simplito/elliptic-php`](https://github.com/simplito/elliptic-php) package to be installed, which requires the `php-gmp` extension to be enabled:
+
+```
+composer require simplito/elliptic-php
+```
+
+# Usage
+
+Use `InteractionType` and `InteractionResponseType` to interpret and respond to webhooks.
+
+Use `InteractionResponseFlags` to make your response special.
+
+Use `verifyKey` to check a request signature. Note you must install the `simplito/elliptic-php` package first. For example:
+
+```php
+use Discord\Interaction;
+use Discord\InteractionResponseType;
+
+$CLIENT_PUBLIC_KEY = getenv('CLIENT_PUBLIC_KEY');
+
+$signature = $_SERVER['HTTP_X_SIGNATURE_ED25519'];
+$timestamp = $_SERVER['HTTP_X_SIGNATURE_TIMESTAMP'];
+$postData = file_get_contents('php://input');
+
+if (Interaction::verifyKey($postData, $signature, $timestamp, $CLIENT_PUBLIC_KEY)) {
+ echo json_encode(array(
+ 'type' => InteractionResponseType::PONG
+ ));
+} else {
+ http_response_code(401);
+ echo "Not verified";
+}
+```
diff --git a/vendor/discord/interactions/composer.json b/vendor/discord/interactions/composer.json
new file mode 100644
index 0000000..903e502
--- /dev/null
+++ b/vendor/discord/interactions/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "discord/interactions",
+ "description": "Utils for implementing the Discord Interactions API",
+ "type": "library",
+ "require-dev": {
+ "simplito/elliptic-php": "^1.0"
+ },
+ "archive": {
+ "exclude": ["!README.md", "!composer.json", "examples", "vendor"]
+ },
+ "license": "MIT",
+ "keywords": ["discord"],
+ "authors": [
+ {
+ "name": "Ian Webster",
+ "email": "ianw_php@ianww.com"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Discord\\": "discord"}
+ },
+ "suggest": {
+ "simplito/elliptic-php": "Required to validate interaction signatures."
+ },
+ "conflict": {
+ "simplito/elliptic-php": "<1.0,>=1.1"
+ }
+}
diff --git a/vendor/discord/interactions/discord/Interaction.php b/vendor/discord/interactions/discord/Interaction.php
new file mode 100644
index 0000000..9cfe5b4
--- /dev/null
+++ b/vendor/discord/interactions/discord/Interaction.php
@@ -0,0 +1,19 @@
+keyFromPublic($client_public_key, 'hex');
+
+ $message = array_merge(unpack('C*', $timestamp), unpack('C*', $rawBody));
+ return $key->verify($message, $signature) == TRUE;
+ }
+}
diff --git a/vendor/discord/interactions/discord/InteractionResponseFlags.php b/vendor/discord/interactions/discord/InteractionResponseFlags.php
new file mode 100644
index 0000000..5b6d681
--- /dev/null
+++ b/vendor/discord/interactions/discord/InteractionResponseFlags.php
@@ -0,0 +1,8 @@
+ InteractionResponseType::PONG
+ ));
+} else {
+ http_response_code(401);
+ echo "Not verified";
+}
diff --git a/vendor/evenement/evenement/.gitattributes b/vendor/evenement/evenement/.gitattributes
new file mode 100644
index 0000000..8e493b8
--- /dev/null
+++ b/vendor/evenement/evenement/.gitattributes
@@ -0,0 +1,7 @@
+/.github export-ignore
+/doc export-ignore
+/examples export-ignore
+/tests export-ignore
+/.gitignore export-ignore
+/CHANGELOG.md export-ignore
+/phpunit.xml.dist export-ignore
diff --git a/vendor/evenement/evenement/LICENSE b/vendor/evenement/evenement/LICENSE
new file mode 100644
index 0000000..d9a37d0
--- /dev/null
+++ b/vendor/evenement/evenement/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Igor Wiedler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/evenement/evenement/README.md b/vendor/evenement/evenement/README.md
new file mode 100644
index 0000000..455dd22
--- /dev/null
+++ b/vendor/evenement/evenement/README.md
@@ -0,0 +1,64 @@
+# Événement
+
+Événement is a very simple event dispatching library for PHP.
+
+It has the same design goals as [Silex](https://silex.symfony.com/) and
+[Pimple](https://github.com/silexphp/Pimple), to empower the user while staying concise
+and simple.
+
+It is very strongly inspired by the [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) API found in
+[node.js](http://nodejs.org).
+
+
+[](https://packagist.org/packages/evenement/evenement)
+[](https://packagist.org/packages/evenement/evenement/stats)
+[](https://packagist.org/packages/evenement/evenement)
+
+## Fetch
+
+The recommended way to install Événement is [through composer](http://getcomposer.org). By running the following command:
+
+ $ composer require evenement/evenement
+
+## Usage
+
+### Creating an Emitter
+
+```php
+on('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Removing Listeners
+
+```php
+removeListener('user.created', function (User $user) use ($logger) {
+ $logger->log(sprintf("User '%s' was created.", $user->getLogin()));
+});
+```
+
+### Emitting Events
+
+```php
+emit('user.created', [$user]);
+```
+
+Tests
+-----
+
+ $ ./vendor/bin/phpunit
+
+License
+-------
+MIT, see LICENSE.
diff --git a/vendor/evenement/evenement/composer.json b/vendor/evenement/evenement/composer.json
new file mode 100644
index 0000000..5444d93
--- /dev/null
+++ b/vendor/evenement/evenement/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "evenement/evenement",
+ "description": "Événement is a very simple event dispatching library for PHP",
+ "keywords": ["event-dispatcher", "event-emitter"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Igor Wiedler",
+ "email": "igor@wiedler.ch"
+ }
+ ],
+ "require": {
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9 || ^6"
+ },
+ "autoload": {
+ "psr-4": {
+ "Evenement\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Evenement\\Tests\\": "tests/"
+ },
+ "files": ["tests/functions.php"]
+ }
+}
diff --git a/vendor/evenement/evenement/src/EventEmitter.php b/vendor/evenement/evenement/src/EventEmitter.php
new file mode 100644
index 0000000..db189b9
--- /dev/null
+++ b/vendor/evenement/evenement/src/EventEmitter.php
@@ -0,0 +1,17 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+class EventEmitter implements EventEmitterInterface
+{
+ use EventEmitterTrait;
+}
diff --git a/vendor/evenement/evenement/src/EventEmitterInterface.php b/vendor/evenement/evenement/src/EventEmitterInterface.php
new file mode 100644
index 0000000..310631a
--- /dev/null
+++ b/vendor/evenement/evenement/src/EventEmitterInterface.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+interface EventEmitterInterface
+{
+ public function on($event, callable $listener);
+ public function once($event, callable $listener);
+ public function removeListener($event, callable $listener);
+ public function removeAllListeners($event = null);
+ public function listeners($event = null);
+ public function emit($event, array $arguments = []);
+}
diff --git a/vendor/evenement/evenement/src/EventEmitterTrait.php b/vendor/evenement/evenement/src/EventEmitterTrait.php
new file mode 100644
index 0000000..1503429
--- /dev/null
+++ b/vendor/evenement/evenement/src/EventEmitterTrait.php
@@ -0,0 +1,154 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Evenement;
+
+use InvalidArgumentException;
+
+use function count;
+use function array_keys;
+use function array_merge;
+use function array_search;
+use function array_unique;
+use function array_values;
+
+trait EventEmitterTrait
+{
+ protected $listeners = [];
+ protected $onceListeners = [];
+
+ public function on($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->listeners[$event])) {
+ $this->listeners[$event] = [];
+ }
+
+ $this->listeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function once($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (!isset($this->onceListeners[$event])) {
+ $this->onceListeners[$event] = [];
+ }
+
+ $this->onceListeners[$event][] = $listener;
+
+ return $this;
+ }
+
+ public function removeListener($event, callable $listener)
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ if (isset($this->listeners[$event])) {
+ $index = array_search($listener, $this->listeners[$event], true);
+ if (false !== $index) {
+ unset($this->listeners[$event][$index]);
+ if (count($this->listeners[$event]) === 0) {
+ unset($this->listeners[$event]);
+ }
+ }
+ }
+
+ if (isset($this->onceListeners[$event])) {
+ $index = array_search($listener, $this->onceListeners[$event], true);
+ if (false !== $index) {
+ unset($this->onceListeners[$event][$index]);
+ if (count($this->onceListeners[$event]) === 0) {
+ unset($this->onceListeners[$event]);
+ }
+ }
+ }
+ }
+
+ public function removeAllListeners($event = null)
+ {
+ if ($event !== null) {
+ unset($this->listeners[$event]);
+ } else {
+ $this->listeners = [];
+ }
+
+ if ($event !== null) {
+ unset($this->onceListeners[$event]);
+ } else {
+ $this->onceListeners = [];
+ }
+ }
+
+ public function listeners($event = null): array
+ {
+ if ($event === null) {
+ $events = [];
+ $eventNames = array_unique(
+ array_merge(
+ array_keys($this->listeners),
+ array_keys($this->onceListeners)
+ )
+ );
+ foreach ($eventNames as $eventName) {
+ $events[$eventName] = array_merge(
+ isset($this->listeners[$eventName]) ? $this->listeners[$eventName] : [],
+ isset($this->onceListeners[$eventName]) ? $this->onceListeners[$eventName] : []
+ );
+ }
+ return $events;
+ }
+
+ return array_merge(
+ isset($this->listeners[$event]) ? $this->listeners[$event] : [],
+ isset($this->onceListeners[$event]) ? $this->onceListeners[$event] : []
+ );
+ }
+
+ public function emit($event, array $arguments = [])
+ {
+ if ($event === null) {
+ throw new InvalidArgumentException('event name must not be null');
+ }
+
+ $listeners = [];
+ if (isset($this->listeners[$event])) {
+ $listeners = array_values($this->listeners[$event]);
+ }
+
+ $onceListeners = [];
+ if (isset($this->onceListeners[$event])) {
+ $onceListeners = array_values($this->onceListeners[$event]);
+ }
+
+ if(empty($listeners) === false) {
+ foreach ($listeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+
+ if(empty($onceListeners) === false) {
+ unset($this->onceListeners[$event]);
+ foreach ($onceListeners as $listener) {
+ $listener(...$arguments);
+ }
+ }
+ }
+}
diff --git a/vendor/fig/http-message-util/.gitignore b/vendor/fig/http-message-util/.gitignore
new file mode 100644
index 0000000..48b8bf9
--- /dev/null
+++ b/vendor/fig/http-message-util/.gitignore
@@ -0,0 +1 @@
+vendor/
diff --git a/vendor/fig/http-message-util/CHANGELOG.md b/vendor/fig/http-message-util/CHANGELOG.md
new file mode 100644
index 0000000..1a02e54
--- /dev/null
+++ b/vendor/fig/http-message-util/CHANGELOG.md
@@ -0,0 +1,147 @@
+# Changelog
+
+All notable changes to this project will be documented in this file, in reverse chronological order by release.
+
+## 1.1.5 - 2020-11-24
+
+### Added
+
+- [#19](https://github.com/php-fig/http-message-util/pull/19) adds support for PHP 8.
+
+### Changed
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 1.1.4 - 2020-02-05
+
+### Added
+
+- Nothing.
+
+### Changed
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- [#15](https://github.com/php-fig/http-message-util/pull/15) removes the dependency on psr/http-message, as it is not technically necessary for usage of this package.
+
+### Fixed
+
+- Nothing.
+
+## 1.1.3 - 2018-11-19
+
+### Added
+
+- [#10](https://github.com/php-fig/http-message-util/pull/10) adds the constants `StatusCodeInterface::STATUS_EARLY_HINTS` (103) and
+ `StatusCodeInterface::STATUS_TOO_EARLY` (425).
+
+### Changed
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 1.1.2 - 2017-02-09
+
+### Added
+
+- [#4](https://github.com/php-fig/http-message-util/pull/4) adds the constant
+ `StatusCodeInterface::STATUS_MISDIRECTED_REQUEST` (421).
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 1.1.1 - 2017-02-06
+
+### Added
+
+- [#3](https://github.com/php-fig/http-message-util/pull/3) adds the constant
+ `StatusCodeInterface::STATUS_IM_A_TEAPOT` (418).
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 1.1.0 - 2016-09-19
+
+### Added
+
+- [#1](https://github.com/php-fig/http-message-util/pull/1) adds
+ `Fig\Http\Message\StatusCodeInterface`, with constants named after common
+ status reason phrases, with values indicating the status codes themselves.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 1.0.0 - 2017-08-05
+
+### Added
+
+- Adds `Fig\Http\Message\RequestMethodInterface`, with constants covering the
+ most common HTTP request methods as specified by the IETF.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
diff --git a/vendor/fig/http-message-util/LICENSE b/vendor/fig/http-message-util/LICENSE
new file mode 100644
index 0000000..e2fa347
--- /dev/null
+++ b/vendor/fig/http-message-util/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2016 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/fig/http-message-util/README.md b/vendor/fig/http-message-util/README.md
new file mode 100644
index 0000000..ea5b5aa
--- /dev/null
+++ b/vendor/fig/http-message-util/README.md
@@ -0,0 +1,17 @@
+# PSR Http Message Util
+
+This repository holds utility classes and constants to facilitate common
+operations of [PSR-7](https://www.php-fig.org/psr/psr-7/); the primary purpose is
+to provide constants for referring to request methods, response status codes and
+messages, and potentially common headers.
+
+Implementation of PSR-7 interfaces is **not** within the scope of this package.
+
+## Installation
+
+Install by adding the package as a [Composer](https://getcomposer.org)
+requirement:
+
+```bash
+$ composer require fig/http-message-util
+```
diff --git a/vendor/fig/http-message-util/composer.json b/vendor/fig/http-message-util/composer.json
new file mode 100644
index 0000000..8645893
--- /dev/null
+++ b/vendor/fig/http-message-util/composer.json
@@ -0,0 +1,28 @@
+{
+ "name": "fig/http-message-util",
+ "description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
+ "keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "require": {
+ "php": "^5.3 || ^7.0 || ^8.0"
+ },
+ "suggest": {
+ "psr/http-message": "The package containing the PSR-7 interfaces"
+ },
+ "autoload": {
+ "psr-4": {
+ "Fig\\Http\\Message\\": "src/"
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ }
+}
diff --git a/vendor/fig/http-message-util/src/RequestMethodInterface.php b/vendor/fig/http-message-util/src/RequestMethodInterface.php
new file mode 100644
index 0000000..97d9a93
--- /dev/null
+++ b/vendor/fig/http-message-util/src/RequestMethodInterface.php
@@ -0,0 +1,34 @@
+
+ * class RequestFactory implements RequestMethodInterface
+ * {
+ * public static function factory(
+ * $uri = '/',
+ * $method = self::METHOD_GET,
+ * $data = []
+ * ) {
+ * }
+ * }
+ *
+ */
+interface RequestMethodInterface
+{
+ const METHOD_HEAD = 'HEAD';
+ const METHOD_GET = 'GET';
+ const METHOD_POST = 'POST';
+ const METHOD_PUT = 'PUT';
+ const METHOD_PATCH = 'PATCH';
+ const METHOD_DELETE = 'DELETE';
+ const METHOD_PURGE = 'PURGE';
+ const METHOD_OPTIONS = 'OPTIONS';
+ const METHOD_TRACE = 'TRACE';
+ const METHOD_CONNECT = 'CONNECT';
+}
diff --git a/vendor/fig/http-message-util/src/StatusCodeInterface.php b/vendor/fig/http-message-util/src/StatusCodeInterface.php
new file mode 100644
index 0000000..99b7e78
--- /dev/null
+++ b/vendor/fig/http-message-util/src/StatusCodeInterface.php
@@ -0,0 +1,107 @@
+
+ * class ResponseFactory implements StatusCodeInterface
+ * {
+ * public function createResponse($code = self::STATUS_OK)
+ * {
+ * }
+ * }
+ *
+ */
+interface StatusCodeInterface
+{
+ // Informational 1xx
+ const STATUS_CONTINUE = 100;
+ const STATUS_SWITCHING_PROTOCOLS = 101;
+ const STATUS_PROCESSING = 102;
+ const STATUS_EARLY_HINTS = 103;
+ // Successful 2xx
+ const STATUS_OK = 200;
+ const STATUS_CREATED = 201;
+ const STATUS_ACCEPTED = 202;
+ const STATUS_NON_AUTHORITATIVE_INFORMATION = 203;
+ const STATUS_NO_CONTENT = 204;
+ const STATUS_RESET_CONTENT = 205;
+ const STATUS_PARTIAL_CONTENT = 206;
+ const STATUS_MULTI_STATUS = 207;
+ const STATUS_ALREADY_REPORTED = 208;
+ const STATUS_IM_USED = 226;
+ // Redirection 3xx
+ const STATUS_MULTIPLE_CHOICES = 300;
+ const STATUS_MOVED_PERMANENTLY = 301;
+ const STATUS_FOUND = 302;
+ const STATUS_SEE_OTHER = 303;
+ const STATUS_NOT_MODIFIED = 304;
+ const STATUS_USE_PROXY = 305;
+ const STATUS_RESERVED = 306;
+ const STATUS_TEMPORARY_REDIRECT = 307;
+ const STATUS_PERMANENT_REDIRECT = 308;
+ // Client Errors 4xx
+ const STATUS_BAD_REQUEST = 400;
+ const STATUS_UNAUTHORIZED = 401;
+ const STATUS_PAYMENT_REQUIRED = 402;
+ const STATUS_FORBIDDEN = 403;
+ const STATUS_NOT_FOUND = 404;
+ const STATUS_METHOD_NOT_ALLOWED = 405;
+ const STATUS_NOT_ACCEPTABLE = 406;
+ const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
+ const STATUS_REQUEST_TIMEOUT = 408;
+ const STATUS_CONFLICT = 409;
+ const STATUS_GONE = 410;
+ const STATUS_LENGTH_REQUIRED = 411;
+ const STATUS_PRECONDITION_FAILED = 412;
+ const STATUS_PAYLOAD_TOO_LARGE = 413;
+ const STATUS_URI_TOO_LONG = 414;
+ const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
+ const STATUS_RANGE_NOT_SATISFIABLE = 416;
+ const STATUS_EXPECTATION_FAILED = 417;
+ const STATUS_IM_A_TEAPOT = 418;
+ const STATUS_MISDIRECTED_REQUEST = 421;
+ const STATUS_UNPROCESSABLE_ENTITY = 422;
+ const STATUS_LOCKED = 423;
+ const STATUS_FAILED_DEPENDENCY = 424;
+ const STATUS_TOO_EARLY = 425;
+ const STATUS_UPGRADE_REQUIRED = 426;
+ const STATUS_PRECONDITION_REQUIRED = 428;
+ const STATUS_TOO_MANY_REQUESTS = 429;
+ const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
+ const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
+ // Server Errors 5xx
+ const STATUS_INTERNAL_SERVER_ERROR = 500;
+ const STATUS_NOT_IMPLEMENTED = 501;
+ const STATUS_BAD_GATEWAY = 502;
+ const STATUS_SERVICE_UNAVAILABLE = 503;
+ const STATUS_GATEWAY_TIMEOUT = 504;
+ const STATUS_VERSION_NOT_SUPPORTED = 505;
+ const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
+ const STATUS_INSUFFICIENT_STORAGE = 507;
+ const STATUS_LOOP_DETECTED = 508;
+ const STATUS_NOT_EXTENDED = 510;
+ const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;
+}
diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md
new file mode 100644
index 0000000..4a2a121
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/CHANGELOG.md
@@ -0,0 +1,485 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 2.8.0 - 2025-08-23
+
+### Added
+
+- Allow empty lists as header values
+
+### Changed
+
+- PHP 8.5 support
+
+## 2.7.1 - 2025-03-27
+
+### Fixed
+
+- Fixed uppercase IPv6 addresses in URI
+
+### Changed
+
+- Improve uploaded file error message
+
+## 2.7.0 - 2024-07-18
+
+### Added
+
+- Add `Utils::redactUserInfo()` method
+- Add ability to encode bools as ints in `Query::build`
+
+## 2.6.3 - 2024-07-18
+
+### Fixed
+
+- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null`
+
+### Changed
+
+- PHP 8.4 support
+
+## 2.6.2 - 2023-12-03
+
+### Fixed
+
+- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints
+
+### Changed
+
+- Updated links in docs to their canonical versions
+- Replaced `call_user_func*` with native calls
+
+## 2.6.1 - 2023-08-27
+
+### Fixed
+
+- Properly handle the fact that PHP transforms numeric strings in array keys to ints
+
+## 2.6.0 - 2023-08-03
+
+### Changed
+
+- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry
+- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload
+
+## 2.5.1 - 2023-08-03
+
+### Fixed
+
+- Corrected mime type for `.acc` files to `audio/aac`
+
+### Changed
+
+- PHP 8.3 support
+
+## 2.5.0 - 2023-04-17
+
+### Changed
+
+- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0`
+
+## 2.4.5 - 2023-04-17
+
+### Fixed
+
+- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec`
+- Fixed `Message::bodySummary` when `preg_match` fails
+- Fixed header validation issue
+
+## 2.4.4 - 2023-03-09
+
+### Changed
+
+- Removed the need for `AllowDynamicProperties` in `LazyOpenStream`
+
+## 2.4.3 - 2022-10-26
+
+### Changed
+
+- Replaced `sha1(uniqid())` by `bin2hex(random_bytes(20))`
+
+## 2.4.2 - 2022-10-25
+
+### Fixed
+
+- Fixed erroneous behaviour when combining host and relative path
+
+## 2.4.1 - 2022-08-28
+
+### Fixed
+
+- Rewind body before reading in `Message::bodySummary`
+
+## 2.4.0 - 2022-06-20
+
+### Added
+
+- Added provisional PHP 8.2 support
+- Added `UriComparator::isCrossOrigin` method
+
+## 2.3.0 - 2022-06-09
+
+### Fixed
+
+- Added `Header::splitList` method
+- Added `Utils::tryGetContents` method
+- Improved `Stream::getContents` method
+- Updated mimetype mappings
+
+## 2.2.2 - 2022-06-08
+
+### Fixed
+
+- Fix `Message::parseRequestUri` for numeric headers
+- Re-wrap exceptions thrown in `fread` into runtime exceptions
+- Throw an exception when multipart options is misformatted
+
+## 2.2.1 - 2022-03-20
+
+### Fixed
+
+- Correct header value validation
+
+## 2.2.0 - 2022-03-20
+
+### Added
+
+- A more compressive list of mime types
+- Add JsonSerializable to Uri
+- Missing return types
+
+### Fixed
+
+- Bug MultipartStream no `uri` metadata
+- Bug MultipartStream with filename for `data://` streams
+- Fixed new line handling in MultipartStream
+- Reduced RAM usage when copying streams
+- Updated parsing in `Header::normalize()`
+
+## 2.1.1 - 2022-03-20
+
+### Fixed
+
+- Validate header values properly
+
+## 2.1.0 - 2021-10-06
+
+### Changed
+
+- Attempting to create a `Uri` object from a malformed URI will no longer throw a generic
+ `InvalidArgumentException`, but rather a `MalformedUriException`, which inherits from the former
+ for backwards compatibility. Callers relying on the exception being thrown to detect invalid
+ URIs should catch the new exception.
+
+### Fixed
+
+- Return `null` in caching stream size if remote size is `null`
+
+## 2.0.0 - 2021-06-30
+
+Identical to the RC release.
+
+## 2.0.0@RC-1 - 2021-04-29
+
+### Fixed
+
+- Handle possibly unset `url` in `stream_get_meta_data`
+
+## 2.0.0@beta-1 - 2021-03-21
+
+### Added
+
+- PSR-17 factories
+- Made classes final
+- PHP7 type hints
+
+### Changed
+
+- When building a query string, booleans are represented as 1 and 0.
+
+### Removed
+
+- PHP < 7.2 support
+- All functions in the `GuzzleHttp\Psr7` namespace
+
+## 1.8.1 - 2021-03-21
+
+### Fixed
+
+- Issue parsing IPv6 URLs
+- Issue modifying ServerRequest lost all its attributes
+
+## 1.8.0 - 2021-03-21
+
+### Added
+
+- Locale independent URL parsing
+- Most classes got a `@final` annotation to prepare for 2.0
+
+### Fixed
+
+- Issue when creating stream from `php://input` and curl-ext is not installed
+- Broken `Utils::tryFopen()` on PHP 8
+
+## 1.7.0 - 2020-09-30
+
+### Added
+
+- Replaced functions by static methods
+
+### Fixed
+
+- Converting a non-seekable stream to a string
+- Handle multiple Set-Cookie correctly
+- Ignore array keys in header values when merging
+- Allow multibyte characters to be parsed in `Message:bodySummary()`
+
+### Changed
+
+- Restored partial HHVM 3 support
+
+
+## [1.6.1] - 2019-07-02
+
+### Fixed
+
+- Accept null and bool header values again
+
+
+## [1.6.0] - 2019-06-30
+
+### Added
+
+- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244)
+- Added MIME type for WEBP image format (#246)
+- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272)
+
+### Changed
+
+- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262)
+- Accept port number 0 to be valid (#270)
+
+### Fixed
+
+- Fixed subsequent reads from `php://input` in ServerRequest (#247)
+- Fixed readable/writable detection for certain stream modes (#248)
+- Fixed encoding of special characters in the `userInfo` component of an URI (#253)
+
+
+## [1.5.2] - 2018-12-04
+
+### Fixed
+
+- Check body size when getting the message summary
+
+
+## [1.5.1] - 2018-12-04
+
+### Fixed
+
+- Get the summary of a body only if it is readable
+
+
+## [1.5.0] - 2018-12-03
+
+### Added
+
+- Response first-line to response string exception (fixes #145)
+- A test for #129 behavior
+- `get_message_body_summary` function in order to get the message summary
+- `3gp` and `mkv` mime types
+
+### Changed
+
+- Clarify exception message when stream is detached
+
+### Deprecated
+
+- Deprecated parsing folded header lines as per RFC 7230
+
+### Fixed
+
+- Fix `AppendStream::detach` to not close streams
+- `InflateStream` preserves `isSeekable` attribute of the underlying stream
+- `ServerRequest::getUriFromGlobals` to support URLs in query parameters
+
+
+Several other fixes and improvements.
+
+
+## [1.4.2] - 2017-03-20
+
+### Fixed
+
+- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
+ calls to `trigger_error` when deprecated methods are invoked.
+
+
+## [1.4.1] - 2017-02-27
+
+### Added
+
+- Rriggering of silenced deprecation warnings.
+
+### Fixed
+
+- Reverted BC break by reintroducing behavior to automagically fix a URI with a
+ relative path and an authority by adding a leading slash to the path. It's only
+ deprecated now.
+
+
+## [1.4.0] - 2017-02-21
+
+### Added
+
+- Added common URI utility methods based on RFC 3986 (see documentation in the readme):
+ - `Uri::isDefaultPort`
+ - `Uri::isAbsolute`
+ - `Uri::isNetworkPathReference`
+ - `Uri::isAbsolutePathReference`
+ - `Uri::isRelativePathReference`
+ - `Uri::isSameDocumentReference`
+ - `Uri::composeComponents`
+ - `UriNormalizer::normalize`
+ - `UriNormalizer::isEquivalent`
+ - `UriResolver::relativize`
+
+### Changed
+
+- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
+- Allow `parse_response` to parse a response without delimiting space and reason.
+- Ensure each URI modification results in a valid URI according to PSR-7 discussions.
+ Invalid modifications will throw an exception instead of returning a wrong URI or
+ doing some magic.
+ - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE
new file mode 100644
index 0000000..51c7ec8
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,26 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Michael Dowling
+Copyright (c) 2015 Márk Sági-Kazár
+Copyright (c) 2015 Graham Campbell
+Copyright (c) 2016 Tobias Schultze
+Copyright (c) 2016 George Mponos
+Copyright (c) 2018 Tobias Nyholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md
new file mode 100644
index 0000000..24aad86
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,887 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+
+
+
+
+## Features
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## Installation
+
+```shell
+composer require guzzlehttp/psr7
+```
+
+## Version Guidance
+
+| Version | Status | PHP Version |
+|---------|---------------------|--------------|
+| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
+| 2.x | Latest | >=7.2.5,<8.6 |
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\Utils::streamFor('abc, ');
+$b = Psr7\Utils::streamFor('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\Utils::streamFor();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\Utils::streamFor('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.
+
+This stream decorator converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ private $stream;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ ($this->callback)();
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Static API
+
+There are various static methods available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `GuzzleHttp\Psr7\Message::toString`
+
+`public static function toString(MessageInterface $message): string`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\Message::toString($request);
+```
+
+
+## `GuzzleHttp\Psr7\Message::bodySummary`
+
+`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null`
+
+Get a short summary of the message body.
+
+Will return `null` if the response is not printable.
+
+
+## `GuzzleHttp\Psr7\Message::rewindBody`
+
+`public static function rewindBody(MessageInterface $message): void`
+
+Attempts to rewind a message body and throws an exception on failure.
+
+The body of the message will only be rewound if a call to `tell()`
+returns a value other than `0`.
+
+
+## `GuzzleHttp\Psr7\Message::parseMessage`
+
+`public static function parseMessage(string $message): array`
+
+Parses an HTTP message into an associative array.
+
+The array contains the "start-line" key containing the start line of
+the message, "headers" key containing an associative array of header
+array values, and a "body" key containing the body of the message.
+
+
+## `GuzzleHttp\Psr7\Message::parseRequestUri`
+
+`public static function parseRequestUri(string $path, array $headers): string`
+
+Constructs a URI for an HTTP request message.
+
+
+## `GuzzleHttp\Psr7\Message::parseRequest`
+
+`public static function parseRequest(string $message): Request`
+
+Parses a request message string into a request object.
+
+
+## `GuzzleHttp\Psr7\Message::parseResponse`
+
+`public static function parseResponse(string $message): Response`
+
+Parses a response message string into a response object.
+
+
+## `GuzzleHttp\Psr7\Header::parse`
+
+`public static function parse(string|array $header): array`
+
+Parse an array of header values containing ";" separated data into an
+array of associative arrays representing the header key value pair data
+of the header. When a parameter does not contain a value, but just
+contains a key, this function will inject a key with a '' string value.
+
+
+## `GuzzleHttp\Psr7\Header::splitList`
+
+`public static function splitList(string|string[] $header): string[]`
+
+Splits a HTTP header defined to contain a comma-separated list into
+each individual value:
+
+```
+$knownEtags = Header::splitList($request->getHeader('if-none-match'));
+```
+
+Example headers include `accept`, `cache-control` and `if-none-match`.
+
+
+## `GuzzleHttp\Psr7\Header::normalize` (deprecated)
+
+`public static function normalize(string|array $header): array`
+
+`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist)
+which performs the same operation with a cleaned up API and improved
+documentation.
+
+Converts an array of header values that may contain comma separated
+headers into an array of headers with no comma separated values.
+
+
+## `GuzzleHttp\Psr7\Query::parse`
+
+`public static function parse(string $str, int|bool $urlEncoding = true): array`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key
+value pair will become an array. This function does not parse nested
+PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
+will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
+
+
+## `GuzzleHttp\Psr7\Query::build`
+
+`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of `parse()` to build a query
+string. This function does not modify the provided keys when an array is
+encountered (like `http_build_query()` would).
+
+
+## `GuzzleHttp\Psr7\Utils::caselessRemove`
+
+`public static function caselessRemove(iterable $keys, $keys, array $data): array`
+
+Remove the items given by the keys, case insensitively from the data.
+
+
+## `GuzzleHttp\Psr7\Utils::copyToStream`
+
+`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void`
+
+Copy the contents of a stream into another stream until the given number
+of bytes have been read.
+
+
+## `GuzzleHttp\Psr7\Utils::copyToString`
+
+`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string`
+
+Copy the contents of a stream into a string until the given number of
+bytes have been read.
+
+
+## `GuzzleHttp\Psr7\Utils::hash`
+
+`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string`
+
+Calculate a hash of a stream.
+
+This method reads the entire stream to calculate a rolling hash, based on
+PHP's `hash_init` functions.
+
+
+## `GuzzleHttp\Psr7\Utils::modifyRequest`
+
+`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface`
+
+Clone and modify a request with the given changes.
+
+This method is useful for reducing the number of clones needed to mutate
+a message.
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `GuzzleHttp\Psr7\Utils::readLine`
+
+`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `GuzzleHttp\Psr7\Utils::redactUserInfo`
+
+`public static function redactUserInfo(UriInterface $uri): UriInterface`
+
+Redact the password in the user info part of a URI.
+
+
+## `GuzzleHttp\Psr7\Utils::streamFor`
+
+`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+- metadata: Array of custom metadata.
+- size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\Utils::streamFor('foo');
+$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100));
+```
+
+
+## `GuzzleHttp\Psr7\Utils::tryFopen`
+
+`public static function tryFopen(string $filename, string $mode): resource`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an
+error handler that checks for errors and throws an exception instead.
+
+
+## `GuzzleHttp\Psr7\Utils::tryGetContents`
+
+`public static function tryGetContents(resource $stream): string`
+
+Safely gets the contents of a given stream.
+
+When stream_get_contents fails, PHP normally raises a warning. This
+function adds an error handler that checks for errors and throws an
+exception instead.
+
+
+## `GuzzleHttp\Psr7\Utils::uriFor`
+
+`public static function uriFor(string|UriInterface $uri): UriInterface`
+
+Returns a UriInterface for the given value.
+
+This function accepts a string or UriInterface and returns a
+UriInterface for the given value. If the value is already a
+UriInterface, it is returned as-is.
+
+
+## `GuzzleHttp\Psr7\MimeType::fromFilename`
+
+`public static function fromFilename(string $filename): string|null`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `GuzzleHttp\Psr7\MimeType::fromExtension`
+
+`public static function fromExtension(string $extension): string|null`
+
+Maps a file extensions to a mimetype.
+
+
+## Upgrading from Function API
+
+The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:
+
+| Original Function | Replacement Method |
+|----------------|----------------|
+| `str` | `Message::toString` |
+| `uri_for` | `Utils::uriFor` |
+| `stream_for` | `Utils::streamFor` |
+| `parse_header` | `Header::parse` |
+| `normalize_header` | `Header::normalize` |
+| `modify_request` | `Utils::modifyRequest` |
+| `rewind_body` | `Message::rewindBody` |
+| `try_fopen` | `Utils::tryFopen` |
+| `copy_to_string` | `Utils::copyToString` |
+| `copy_to_stream` | `Utils::copyToStream` |
+| `hash` | `Utils::hash` |
+| `readline` | `Utils::readLine` |
+| `parse_request` | `Message::parseRequest` |
+| `parse_response` | `Message::parseResponse` |
+| `parse_query` | `Query::parse` |
+| `build_query` | `Query::build` |
+| `mimetype_from_filename` | `MimeType::fromFilename` |
+| `mimetype_from_extension` | `MimeType::fromExtension` |
+| `_parse_message` | `Message::parseMessage` |
+| `_parse_request_uri` | `Message::parseRequestUri` |
+| `get_message_body_summary` | `Message::bodySummary` |
+| `_caseless_remove` | `Utils::caselessRemove` |
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need
+to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Cross-Origin Detection
+
+`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin.
+
+### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin`
+
+`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool`
+
+Determines if a modified URL should be considered cross-origin with respect to an original URL.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web
+browsers do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
+
+
+## Security
+
+If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information.
+
+
+## License
+
+Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
+
+
+## For Enterprise
+
+Available as part of the Tidelift Subscription
+
+The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json
new file mode 100644
index 0000000..96098f5
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,93 @@
+{
+ "name": "guzzlehttp/psr7",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "request",
+ "response",
+ "message",
+ "stream",
+ "http",
+ "uri",
+ "url",
+ "psr-7"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "bamarni/composer-bin-plugin": true
+ },
+ "preferred-install": "dist",
+ "sort-packages": true
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100644
index 0000000..ee8f378
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,248 @@
+addStream($stream);
+ }
+ }
+
+ public function __toString(): string
+ {
+ try {
+ $this->rewind();
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream): void
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents(): string
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ */
+ public function close(): void
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ */
+ public function getSize(): ?int
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof(): bool
+ {
+ return !$this->streams
+ || ($this->current >= count($this->streams) - 1
+ && $this->streams[$this->current]->eof());
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ */
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ .$i.' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ */
+ public function read($length): string
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ ++$this->current;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ if ($result === '') {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->seekable;
+ }
+
+ public function write($string): int
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100644
index 0000000..2b0eb77
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,147 @@
+hwm = $hwm;
+ }
+
+ public function __toString(): string
+ {
+ return $this->getContents();
+ }
+
+ public function getContents(): string
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close(): void
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+
+ return null;
+ }
+
+ public function getSize(): ?int
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function isWritable(): bool
+ {
+ return true;
+ }
+
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof(): bool
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell(): int
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length): string
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string): int
+ {
+ $this->buffer .= $string;
+
+ if (strlen($this->buffer) >= $this->hwm) {
+ return 0;
+ }
+
+ return strlen($string);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if ($key === 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100644
index 0000000..7e4554d
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,153 @@
+remoteStream = $stream;
+ $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));
+ }
+
+ public function getSize(): ?int
+ {
+ $remoteSize = $this->remoteStream->getSize();
+
+ if (null === $remoteSize) {
+ return null;
+ }
+
+ return max($this->stream->getSize(), $remoteSize);
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if ($whence === SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence === SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence === SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length): string
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string): int
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof(): bool
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close(): void
+ {
+ $this->remoteStream->close();
+ $this->stream->close();
+ }
+
+ private function cacheEntireStream(): int
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ Utils::copyToStream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100644
index 0000000..6e3d209
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,49 @@
+stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string): int
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php
new file mode 100644
index 0000000..3a08477
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php
@@ -0,0 +1,14 @@
+ */
+ private $methods;
+
+ /**
+ * @param array $methods Hash of method name to a callable.
+ */
+ public function __construct(array $methods)
+ {
+ $this->methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_'.$name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __get(string $name): void
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ .'() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ ($this->_fn_close)();
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ *
+ * @throws \LogicException
+ */
+ public function __wakeup(): void
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) {
+ /** @var callable $callable */
+ $callable = [$stream, $diff];
+ $methods[$diff] = $callable;
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString(): string
+ {
+ try {
+ /** @var string */
+ return ($this->_fn___toString)();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function close(): void
+ {
+ ($this->_fn_close)();
+ }
+
+ public function detach()
+ {
+ return ($this->_fn_detach)();
+ }
+
+ public function getSize(): ?int
+ {
+ return ($this->_fn_getSize)();
+ }
+
+ public function tell(): int
+ {
+ return ($this->_fn_tell)();
+ }
+
+ public function eof(): bool
+ {
+ return ($this->_fn_eof)();
+ }
+
+ public function isSeekable(): bool
+ {
+ return ($this->_fn_isSeekable)();
+ }
+
+ public function rewind(): void
+ {
+ ($this->_fn_rewind)();
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ ($this->_fn_seek)($offset, $whence);
+ }
+
+ public function isWritable(): bool
+ {
+ return ($this->_fn_isWritable)();
+ }
+
+ public function write($string): int
+ {
+ return ($this->_fn_write)($string);
+ }
+
+ public function isReadable(): bool
+ {
+ return ($this->_fn_isReadable)();
+ }
+
+ public function read($length): string
+ {
+ return ($this->_fn_read)($length);
+ }
+
+ public function getContents(): string
+ {
+ return ($this->_fn_getContents)();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return ($this->_fn_getMetadata)($key);
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php
new file mode 100644
index 0000000..bbce8b0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Header.php
@@ -0,0 +1,134 @@
+]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @deprecated Use self::splitList() instead.
+ */
+ public static function normalize($header): array
+ {
+ $result = [];
+ foreach ((array) $header as $value) {
+ foreach (self::splitList($value) as $parsed) {
+ $result[] = $parsed;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Splits a HTTP header defined to contain a comma-separated list into
+ * each individual value. Empty values will be removed.
+ *
+ * Example headers include 'accept', 'cache-control' and 'if-none-match'.
+ *
+ * This method must not be used to parse headers that are not defined as
+ * a list, such as 'user-agent' or 'set-cookie'.
+ *
+ * @param string|string[] $values Header value as returned by MessageInterface::getHeader()
+ *
+ * @return string[]
+ */
+ public static function splitList($values): array
+ {
+ if (!\is_array($values)) {
+ $values = [$values];
+ }
+
+ $result = [];
+ foreach ($values as $value) {
+ if (!\is_string($value)) {
+ throw new \TypeError('$header must either be a string or an array containing strings.');
+ }
+
+ $v = '';
+ $isQuoted = false;
+ $isEscaped = false;
+ for ($i = 0, $max = \strlen($value); $i < $max; ++$i) {
+ if ($isEscaped) {
+ $v .= $value[$i];
+ $isEscaped = false;
+
+ continue;
+ }
+
+ if (!$isQuoted && $value[$i] === ',') {
+ $v = \trim($v);
+ if ($v !== '') {
+ $result[] = $v;
+ }
+
+ $v = '';
+ continue;
+ }
+
+ if ($isQuoted && $value[$i] === '\\') {
+ $isEscaped = true;
+ $v .= $value[$i];
+
+ continue;
+ }
+ if ($value[$i] === '"') {
+ $isQuoted = !$isQuoted;
+ $v .= $value[$i];
+
+ continue;
+ }
+
+ $v .= $value[$i];
+ }
+
+ $v = \trim($v);
+ if ($v !== '') {
+ $result[] = $v;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php
new file mode 100644
index 0000000..3ef1510
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php
@@ -0,0 +1,94 @@
+getSize();
+ }
+
+ return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
+ }
+
+ public function createStream(string $content = ''): StreamInterface
+ {
+ return Utils::streamFor($content);
+ }
+
+ public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface
+ {
+ try {
+ $resource = Utils::tryFopen($file, $mode);
+ } catch (\RuntimeException $e) {
+ if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e);
+ }
+
+ throw $e;
+ }
+
+ return Utils::streamFor($resource);
+ }
+
+ public function createStreamFromResource($resource): StreamInterface
+ {
+ return Utils::streamFor($resource);
+ }
+
+ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
+ {
+ if (empty($method)) {
+ if (!empty($serverParams['REQUEST_METHOD'])) {
+ $method = $serverParams['REQUEST_METHOD'];
+ } else {
+ throw new \InvalidArgumentException('Cannot determine HTTP method');
+ }
+ }
+
+ return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
+ }
+
+ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
+ {
+ return new Response($code, [], null, '1.1', $reasonPhrase);
+ }
+
+ public function createRequest(string $method, $uri): RequestInterface
+ {
+ return new Request($method, $uri);
+ }
+
+ public function createUri(string $uri = ''): UriInterface
+ {
+ return new Uri($uri);
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100644
index 0000000..e674c9a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,37 @@
+ 15 + 32]);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100644
index 0000000..f6c8490
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,49 @@
+filename = $filename;
+ $this->mode = $mode;
+
+ // unsetting the property forces the first access to go through
+ // __get().
+ unset($this->stream);
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ */
+ protected function createStream(): StreamInterface
+ {
+ return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100644
index 0000000..fb22325
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,157 @@
+stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof(): bool
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit === -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ */
+ public function getSize(): ?int
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit === -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ */
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ */
+ public function tell(): int
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset(int $offset): void
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit(int $limit): void
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length): string
+ {
+ if ($this->limit === -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php
new file mode 100644
index 0000000..5561a51
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Message.php
@@ -0,0 +1,246 @@
+getMethod().' '
+ .$message->getRequestTarget())
+ .' HTTP/'.$message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: ".$message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/'.$message->getProtocolVersion().' '
+ .$message->getStatusCode().' '
+ .$message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ if (is_string($name) && strtolower($name) === 'set-cookie') {
+ foreach ($values as $value) {
+ $msg .= "\r\n{$name}: ".$value;
+ }
+ } else {
+ $msg .= "\r\n{$name}: ".implode(', ', $values);
+ }
+ }
+
+ return "{$msg}\r\n\r\n".$message->getBody();
+ }
+
+ /**
+ * Get a short summary of the message body.
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ */
+ public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
+ {
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $body->rewind();
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()`
+ * returns a value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+ public static function rewindBody(MessageInterface $message): void
+ {
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+ }
+
+ /**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ */
+ public static function parseMessage(string $message): array
+ {
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ [$rawHeaders, $body] = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ [$startLine, $rawHeaders] = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+ }
+
+ /**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ */
+ public static function parseRequestUri(string $path, array $headers): string
+ {
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ // Numeric array keys are converted to int by PHP.
+ $k = (string) $k;
+
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme.'://'.$host.'/'.ltrim($path, '/');
+ }
+
+ /**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ */
+ public static function parseRequest(string $message): RequestInterface
+ {
+ $data = self::parseMessage($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+ }
+
+ /**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ */
+ public static function parseResponse(string $message): ResponseInterface
+ {
+ $data = self::parseMessage($message);
+ // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
+ // the space between status-code and reason-phrase is required. But
+ // browsers accept responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ (int) $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ $parts[2] ?? null
+ );
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100644
index 0000000..c15ee63
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,261 @@
+ array of values */
+ private $headers = [];
+
+ /** @var string[] Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface|null */
+ private $stream;
+
+ public function getProtocolVersion(): string
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version): MessageInterface
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+
+ return $new;
+ }
+
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header): bool
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header): array
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header): string
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value): MessageInterface
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value): MessageInterface
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header): MessageInterface
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody(): StreamInterface
+ {
+ if (!$this->stream) {
+ $this->stream = Utils::streamFor('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body): MessageInterface
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+
+ return $new;
+ }
+
+ /**
+ * @param (string|string[])[] $headers
+ */
+ private function setHeaders(array $headers): void
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ // Numeric array keys are converted to int by PHP.
+ $header = (string) $header;
+
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ /**
+ * @param mixed $value
+ *
+ * @return string[]
+ */
+ private function normalizeHeaderValue($value): array
+ {
+ if (!is_array($value)) {
+ return $this->trimAndValidateHeaderValues([$value]);
+ }
+
+ return $this->trimAndValidateHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param mixed[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
+ */
+ private function trimAndValidateHeaderValues(array $values): array
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ $trimmed = trim((string) $value, " \t");
+ $this->assertValue($trimmed);
+
+ return $trimmed;
+ }, array_values($values));
+ }
+
+ /**
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+ *
+ * @param mixed $header
+ */
+ private function assertHeader($header): void
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
+ throw new \InvalidArgumentException(
+ sprintf('"%s" is not valid header name.', $header)
+ );
+ }
+ }
+
+ /**
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+ *
+ * field-value = *( field-content / obs-fold )
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+ * field-vchar = VCHAR / obs-text
+ * VCHAR = %x21-7E
+ * obs-text = %x80-FF
+ * obs-fold = CRLF 1*( SP / HTAB )
+ */
+ private function assertValue(string $value): void
+ {
+ // The regular expression intentionally does not support the obs-fold production, because as
+ // per RFC 7230#3.2.4:
+ //
+ // A sender MUST NOT generate a message that includes
+ // line folding (i.e., that has any field-value that contains a match to
+ // the obs-fold rule) unless the message is intended for packaging
+ // within the message/http media type.
+ //
+ // Clients must not send a request with line folding and a server sending folded headers is
+ // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
+ // folding is not likely to break any legitimate use case.
+ if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
+ throw new \InvalidArgumentException(
+ sprintf('"%s" is not valid header value.', $value)
+ );
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php
new file mode 100644
index 0000000..b131bdb
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MimeType.php
@@ -0,0 +1,1259 @@
+ 'application/vnd.1000minds.decision-model+xml',
+ '3dml' => 'text/vnd.in3d.3dml',
+ '3ds' => 'image/x-3ds',
+ '3g2' => 'video/3gpp2',
+ '3gp' => 'video/3gp',
+ '3gpp' => 'video/3gpp',
+ '3mf' => 'model/3mf',
+ '7z' => 'application/x-7z-compressed',
+ '7zip' => 'application/x-7z-compressed',
+ '123' => 'application/vnd.lotus-1-2-3',
+ 'aab' => 'application/x-authorware-bin',
+ 'aac' => 'audio/aac',
+ 'aam' => 'application/x-authorware-map',
+ 'aas' => 'application/x-authorware-seg',
+ 'abw' => 'application/x-abiword',
+ 'ac' => 'application/vnd.nokia.n-gage.ac+xml',
+ 'ac3' => 'audio/ac3',
+ 'acc' => 'application/vnd.americandynamics.acc',
+ 'ace' => 'application/x-ace-compressed',
+ 'acu' => 'application/vnd.acucobol',
+ 'acutc' => 'application/vnd.acucorp',
+ 'adp' => 'audio/adpcm',
+ 'adts' => 'audio/aac',
+ 'aep' => 'application/vnd.audiograph',
+ 'afm' => 'application/x-font-type1',
+ 'afp' => 'application/vnd.ibm.modcap',
+ 'age' => 'application/vnd.age',
+ 'ahead' => 'application/vnd.ahead.space',
+ 'ai' => 'application/pdf',
+ 'aif' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
+ 'ait' => 'application/vnd.dvb.ait',
+ 'ami' => 'application/vnd.amiga.ami',
+ 'aml' => 'application/automationml-aml+xml',
+ 'amlx' => 'application/automationml-amlx+zip',
+ 'amr' => 'audio/amr',
+ 'apk' => 'application/vnd.android.package-archive',
+ 'apng' => 'image/apng',
+ 'appcache' => 'text/cache-manifest',
+ 'appinstaller' => 'application/appinstaller',
+ 'application' => 'application/x-ms-application',
+ 'appx' => 'application/appx',
+ 'appxbundle' => 'application/appxbundle',
+ 'apr' => 'application/vnd.lotus-approach',
+ 'arc' => 'application/x-freearc',
+ 'arj' => 'application/x-arj',
+ 'asc' => 'application/pgp-signature',
+ 'asf' => 'video/x-ms-asf',
+ 'asm' => 'text/x-asm',
+ 'aso' => 'application/vnd.accpac.simply.aso',
+ 'asx' => 'video/x-ms-asf',
+ 'atc' => 'application/vnd.acucorp',
+ 'atom' => 'application/atom+xml',
+ 'atomcat' => 'application/atomcat+xml',
+ 'atomdeleted' => 'application/atomdeleted+xml',
+ 'atomsvc' => 'application/atomsvc+xml',
+ 'atx' => 'application/vnd.antix.game-component',
+ 'au' => 'audio/x-au',
+ 'avci' => 'image/avci',
+ 'avcs' => 'image/avcs',
+ 'avi' => 'video/x-msvideo',
+ 'avif' => 'image/avif',
+ 'aw' => 'application/applixware',
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
+ 'azv' => 'image/vnd.airzip.accelerator.azv',
+ 'azw' => 'application/vnd.amazon.ebook',
+ 'b16' => 'image/vnd.pco.b16',
+ 'bat' => 'application/x-msdownload',
+ 'bcpio' => 'application/x-bcpio',
+ 'bdf' => 'application/x-font-bdf',
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
+ 'bdoc' => 'application/x-bdoc',
+ 'bed' => 'application/vnd.realvnc.bed',
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
+ 'bin' => 'application/octet-stream',
+ 'blb' => 'application/x-blorb',
+ 'blorb' => 'application/x-blorb',
+ 'bmi' => 'application/vnd.bmi',
+ 'bmml' => 'application/vnd.balsamiq.bmml+xml',
+ 'bmp' => 'image/bmp',
+ 'book' => 'application/vnd.framemaker',
+ 'box' => 'application/vnd.previewsystems.box',
+ 'boz' => 'application/x-bzip2',
+ 'bpk' => 'application/octet-stream',
+ 'bpmn' => 'application/octet-stream',
+ 'bsp' => 'model/vnd.valve.source.compiled-map',
+ 'btf' => 'image/prs.btif',
+ 'btif' => 'image/prs.btif',
+ 'buffer' => 'application/octet-stream',
+ 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2',
+ 'c' => 'text/x-c',
+ 'c4d' => 'application/vnd.clonk.c4group',
+ 'c4f' => 'application/vnd.clonk.c4group',
+ 'c4g' => 'application/vnd.clonk.c4group',
+ 'c4p' => 'application/vnd.clonk.c4group',
+ 'c4u' => 'application/vnd.clonk.c4group',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+ 'caf' => 'audio/x-caf',
+ 'cap' => 'application/vnd.tcpdump.pcap',
+ 'car' => 'application/vnd.curl.car',
+ 'cat' => 'application/vnd.ms-pki.seccat',
+ 'cb7' => 'application/x-cbr',
+ 'cba' => 'application/x-cbr',
+ 'cbr' => 'application/x-cbr',
+ 'cbt' => 'application/x-cbr',
+ 'cbz' => 'application/x-cbr',
+ 'cc' => 'text/x-c',
+ 'cco' => 'application/x-cocoa',
+ 'cct' => 'application/x-director',
+ 'ccxml' => 'application/ccxml+xml',
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
+ 'cdf' => 'application/x-netcdf',
+ 'cdfx' => 'application/cdfx+xml',
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
+ 'cdmia' => 'application/cdmi-capability',
+ 'cdmic' => 'application/cdmi-container',
+ 'cdmid' => 'application/cdmi-domain',
+ 'cdmio' => 'application/cdmi-object',
+ 'cdmiq' => 'application/cdmi-queue',
+ 'cdr' => 'application/cdr',
+ 'cdx' => 'chemical/x-cdx',
+ 'cdxml' => 'application/vnd.chemdraw+xml',
+ 'cdy' => 'application/vnd.cinderella',
+ 'cer' => 'application/pkix-cert',
+ 'cfs' => 'application/x-cfs-compressed',
+ 'cgm' => 'image/cgm',
+ 'chat' => 'application/x-chat',
+ 'chm' => 'application/vnd.ms-htmlhelp',
+ 'chrt' => 'application/vnd.kde.kchart',
+ 'cif' => 'chemical/x-cif',
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
+ 'cil' => 'application/vnd.ms-artgalry',
+ 'cjs' => 'application/node',
+ 'cla' => 'application/vnd.claymore',
+ 'class' => 'application/octet-stream',
+ 'cld' => 'model/vnd.cld',
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
+ 'clkp' => 'application/vnd.crick.clicker.palette',
+ 'clkt' => 'application/vnd.crick.clicker.template',
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
+ 'clkx' => 'application/vnd.crick.clicker',
+ 'clp' => 'application/x-msclip',
+ 'cmc' => 'application/vnd.cosmocaller',
+ 'cmdf' => 'chemical/x-cmdf',
+ 'cml' => 'chemical/x-cml',
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
+ 'cmx' => 'image/x-cmx',
+ 'cod' => 'application/vnd.rim.cod',
+ 'coffee' => 'text/coffeescript',
+ 'com' => 'application/x-msdownload',
+ 'conf' => 'text/plain',
+ 'cpio' => 'application/x-cpio',
+ 'cpl' => 'application/cpl+xml',
+ 'cpp' => 'text/x-c',
+ 'cpt' => 'application/mac-compactpro',
+ 'crd' => 'application/x-mscardfile',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'crx' => 'application/x-chrome-extension',
+ 'cryptonote' => 'application/vnd.rig.cryptonote',
+ 'csh' => 'application/x-csh',
+ 'csl' => 'application/vnd.citationstyles.style+xml',
+ 'csml' => 'chemical/x-csml',
+ 'csp' => 'application/vnd.commonspace',
+ 'csr' => 'application/octet-stream',
+ 'css' => 'text/css',
+ 'cst' => 'application/x-director',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'curl' => 'text/vnd.curl',
+ 'cwl' => 'application/cwl',
+ 'cww' => 'application/prs.cww',
+ 'cxt' => 'application/x-director',
+ 'cxx' => 'text/x-c',
+ 'dae' => 'model/vnd.collada+xml',
+ 'daf' => 'application/vnd.mobius.daf',
+ 'dart' => 'application/vnd.dart',
+ 'dataless' => 'application/vnd.fdsn.seed',
+ 'davmount' => 'application/davmount+xml',
+ 'dbf' => 'application/vnd.dbf',
+ 'dbk' => 'application/docbook+xml',
+ 'dcr' => 'application/x-director',
+ 'dcurl' => 'text/vnd.curl.dcurl',
+ 'dd2' => 'application/vnd.oma.dd2+xml',
+ 'ddd' => 'application/vnd.fujixerox.ddd',
+ 'ddf' => 'application/vnd.syncml.dmddf+xml',
+ 'dds' => 'image/vnd.ms-dds',
+ 'deb' => 'application/x-debian-package',
+ 'def' => 'text/plain',
+ 'deploy' => 'application/octet-stream',
+ 'der' => 'application/x-x509-ca-cert',
+ 'dfac' => 'application/vnd.dreamfactory',
+ 'dgc' => 'application/x-dgc-compressed',
+ 'dib' => 'image/bmp',
+ 'dic' => 'text/x-c',
+ 'dir' => 'application/x-director',
+ 'dis' => 'application/vnd.mobius.dis',
+ 'disposition-notification' => 'message/disposition-notification',
+ 'dist' => 'application/octet-stream',
+ 'distz' => 'application/octet-stream',
+ 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'dll' => 'application/octet-stream',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'dmn' => 'application/octet-stream',
+ 'dmp' => 'application/vnd.tcpdump.pcap',
+ 'dms' => 'application/octet-stream',
+ 'dna' => 'application/vnd.dna',
+ 'doc' => 'application/msword',
+ 'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dot' => 'application/msword',
+ 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'dp' => 'application/vnd.osgi.dp',
+ 'dpg' => 'application/vnd.dpgraph',
+ 'dpx' => 'image/dpx',
+ 'dra' => 'audio/vnd.dra',
+ 'drle' => 'image/dicom-rle',
+ 'dsc' => 'text/prs.lines.tag',
+ 'dssc' => 'application/dssc+der',
+ 'dtb' => 'application/x-dtbook+xml',
+ 'dtd' => 'application/xml-dtd',
+ 'dts' => 'audio/vnd.dts',
+ 'dtshd' => 'audio/vnd.dts.hd',
+ 'dump' => 'application/octet-stream',
+ 'dvb' => 'video/vnd.dvb.file',
+ 'dvi' => 'application/x-dvi',
+ 'dwd' => 'application/atsc-dwd+xml',
+ 'dwf' => 'model/vnd.dwf',
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'dxp' => 'application/vnd.spotfire.dxp',
+ 'dxr' => 'application/x-director',
+ 'ear' => 'application/java-archive',
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
+ 'ecma' => 'application/ecmascript',
+ 'edm' => 'application/vnd.novadigm.edm',
+ 'edx' => 'application/vnd.novadigm.edx',
+ 'efif' => 'application/vnd.picsel',
+ 'ei6' => 'application/vnd.pg.osasli',
+ 'elc' => 'application/octet-stream',
+ 'emf' => 'image/emf',
+ 'eml' => 'message/rfc822',
+ 'emma' => 'application/emma+xml',
+ 'emotionml' => 'application/emotionml+xml',
+ 'emz' => 'application/x-msmetafile',
+ 'eol' => 'audio/vnd.digital-winds',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'es3' => 'application/vnd.eszigno3+xml',
+ 'esa' => 'application/vnd.osgi.subsystem',
+ 'esf' => 'application/vnd.epson.esf',
+ 'et3' => 'application/vnd.eszigno3+xml',
+ 'etx' => 'text/x-setext',
+ 'eva' => 'application/x-eva',
+ 'evy' => 'application/x-envoy',
+ 'exe' => 'application/octet-stream',
+ 'exi' => 'application/exi',
+ 'exp' => 'application/express',
+ 'exr' => 'image/aces',
+ 'ext' => 'application/vnd.novadigm.ext',
+ 'ez' => 'application/andrew-inset',
+ 'ez2' => 'application/vnd.ezpix-album',
+ 'ez3' => 'application/vnd.ezpix-package',
+ 'f' => 'text/x-fortran',
+ 'f4v' => 'video/mp4',
+ 'f77' => 'text/x-fortran',
+ 'f90' => 'text/x-fortran',
+ 'fbs' => 'image/vnd.fastbidsheet',
+ 'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
+ 'fcs' => 'application/vnd.isac.fcs',
+ 'fdf' => 'application/vnd.fdf',
+ 'fdt' => 'application/fdt+xml',
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
+ 'fgd' => 'application/x-director',
+ 'fh' => 'image/x-freehand',
+ 'fh4' => 'image/x-freehand',
+ 'fh5' => 'image/x-freehand',
+ 'fh7' => 'image/x-freehand',
+ 'fhc' => 'image/x-freehand',
+ 'fig' => 'application/x-xfig',
+ 'fits' => 'image/fits',
+ 'flac' => 'audio/x-flac',
+ 'fli' => 'video/x-fli',
+ 'flo' => 'application/vnd.micrografx.flo',
+ 'flv' => 'video/x-flv',
+ 'flw' => 'application/vnd.kde.kivio',
+ 'flx' => 'text/vnd.fmi.flexstor',
+ 'fly' => 'text/vnd.fly',
+ 'fm' => 'application/vnd.framemaker',
+ 'fnc' => 'application/vnd.frogans.fnc',
+ 'fo' => 'application/vnd.software602.filler.form+xml',
+ 'for' => 'text/x-fortran',
+ 'fpx' => 'image/vnd.fpx',
+ 'frame' => 'application/vnd.framemaker',
+ 'fsc' => 'application/vnd.fsc.weblaunch',
+ 'fst' => 'image/vnd.fst',
+ 'ftc' => 'application/vnd.fluxtime.clip',
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
+ 'fvt' => 'video/vnd.fvt',
+ 'fxp' => 'application/vnd.adobe.fxp',
+ 'fxpl' => 'application/vnd.adobe.fxp',
+ 'fzs' => 'application/vnd.fuzzysheet',
+ 'g2w' => 'application/vnd.geoplan',
+ 'g3' => 'image/g3fax',
+ 'g3w' => 'application/vnd.geospace',
+ 'gac' => 'application/vnd.groove-account',
+ 'gam' => 'application/x-tads',
+ 'gbr' => 'application/rpki-ghostbusters',
+ 'gca' => 'application/x-gca-compressed',
+ 'gdl' => 'model/vnd.gdl',
+ 'gdoc' => 'application/vnd.google-apps.document',
+ 'ged' => 'text/vnd.familysearch.gedcom',
+ 'geo' => 'application/vnd.dynageo',
+ 'geojson' => 'application/geo+json',
+ 'gex' => 'application/vnd.geometry-explorer',
+ 'ggb' => 'application/vnd.geogebra.file',
+ 'ggt' => 'application/vnd.geogebra.tool',
+ 'ghf' => 'application/vnd.groove-help',
+ 'gif' => 'image/gif',
+ 'gim' => 'application/vnd.groove-identity-message',
+ 'glb' => 'model/gltf-binary',
+ 'gltf' => 'model/gltf+json',
+ 'gml' => 'application/gml+xml',
+ 'gmx' => 'application/vnd.gmx',
+ 'gnumeric' => 'application/x-gnumeric',
+ 'gpg' => 'application/gpg-keys',
+ 'gph' => 'application/vnd.flographit',
+ 'gpx' => 'application/gpx+xml',
+ 'gqf' => 'application/vnd.grafeq',
+ 'gqs' => 'application/vnd.grafeq',
+ 'gram' => 'application/srgs',
+ 'gramps' => 'application/x-gramps-xml',
+ 'gre' => 'application/vnd.geometry-explorer',
+ 'grv' => 'application/vnd.groove-injector',
+ 'grxml' => 'application/srgs+xml',
+ 'gsf' => 'application/x-font-ghostscript',
+ 'gsheet' => 'application/vnd.google-apps.spreadsheet',
+ 'gslides' => 'application/vnd.google-apps.presentation',
+ 'gtar' => 'application/x-gtar',
+ 'gtm' => 'application/vnd.groove-tool-message',
+ 'gtw' => 'model/vnd.gtw',
+ 'gv' => 'text/vnd.graphviz',
+ 'gxf' => 'application/gxf',
+ 'gxt' => 'application/vnd.geonext',
+ 'gz' => 'application/gzip',
+ 'gzip' => 'application/gzip',
+ 'h' => 'text/x-c',
+ 'h261' => 'video/h261',
+ 'h263' => 'video/h263',
+ 'h264' => 'video/h264',
+ 'hal' => 'application/vnd.hal+xml',
+ 'hbci' => 'application/vnd.hbci',
+ 'hbs' => 'text/x-handlebars-template',
+ 'hdd' => 'application/x-virtualbox-hdd',
+ 'hdf' => 'application/x-hdf',
+ 'heic' => 'image/heic',
+ 'heics' => 'image/heic-sequence',
+ 'heif' => 'image/heif',
+ 'heifs' => 'image/heif-sequence',
+ 'hej2' => 'image/hej2k',
+ 'held' => 'application/atsc-held+xml',
+ 'hh' => 'text/x-c',
+ 'hjson' => 'application/hjson',
+ 'hlp' => 'application/winhlp',
+ 'hpgl' => 'application/vnd.hp-hpgl',
+ 'hpid' => 'application/vnd.hp-hpid',
+ 'hps' => 'application/vnd.hp-hps',
+ 'hqx' => 'application/mac-binhex40',
+ 'hsj2' => 'image/hsj2',
+ 'htc' => 'text/x-component',
+ 'htke' => 'application/vnd.kenameaapp',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
+ 'hvs' => 'application/vnd.yamaha.hv-script',
+ 'i2g' => 'application/vnd.intergeo',
+ 'icc' => 'application/vnd.iccprofile',
+ 'ice' => 'x-conference/x-cooltalk',
+ 'icm' => 'application/vnd.iccprofile',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ief' => 'image/ief',
+ 'ifb' => 'text/calendar',
+ 'ifm' => 'application/vnd.shana.informed.formdata',
+ 'iges' => 'model/iges',
+ 'igl' => 'application/vnd.igloader',
+ 'igm' => 'application/vnd.insors.igm',
+ 'igs' => 'model/iges',
+ 'igx' => 'application/vnd.micrografx.igx',
+ 'iif' => 'application/vnd.shana.informed.interchange',
+ 'img' => 'application/octet-stream',
+ 'imp' => 'application/vnd.accpac.simply.imp',
+ 'ims' => 'application/vnd.ms-ims',
+ 'in' => 'text/plain',
+ 'ini' => 'text/plain',
+ 'ink' => 'application/inkml+xml',
+ 'inkml' => 'application/inkml+xml',
+ 'install' => 'application/x-install-instructions',
+ 'iota' => 'application/vnd.astraea-software.iota',
+ 'ipfix' => 'application/ipfix',
+ 'ipk' => 'application/vnd.shana.informed.package',
+ 'irm' => 'application/vnd.ibm.rights-management',
+ 'irp' => 'application/vnd.irepository.package+xml',
+ 'iso' => 'application/x-iso9660-image',
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
+ 'its' => 'application/its+xml',
+ 'ivp' => 'application/vnd.immervision-ivp',
+ 'ivu' => 'application/vnd.immervision-ivu',
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
+ 'jade' => 'text/jade',
+ 'jam' => 'application/vnd.jam',
+ 'jar' => 'application/java-archive',
+ 'jardiff' => 'application/x-java-archive-diff',
+ 'java' => 'text/x-java-source',
+ 'jhc' => 'image/jphc',
+ 'jisp' => 'application/vnd.jisp',
+ 'jls' => 'image/jls',
+ 'jlt' => 'application/vnd.hp-jlyt',
+ 'jng' => 'image/x-jng',
+ 'jnlp' => 'application/x-java-jnlp-file',
+ 'joda' => 'application/vnd.joost.joda-archive',
+ 'jp2' => 'image/jp2',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpf' => 'image/jpx',
+ 'jpg' => 'image/jpeg',
+ 'jpg2' => 'image/jp2',
+ 'jpgm' => 'video/jpm',
+ 'jpgv' => 'video/jpeg',
+ 'jph' => 'image/jph',
+ 'jpm' => 'video/jpm',
+ 'jpx' => 'image/jpx',
+ 'js' => 'application/javascript',
+ 'json' => 'application/json',
+ 'json5' => 'application/json5',
+ 'jsonld' => 'application/ld+json',
+ 'jsonml' => 'application/jsonml+json',
+ 'jsx' => 'text/jsx',
+ 'jt' => 'model/jt',
+ 'jxr' => 'image/jxr',
+ 'jxra' => 'image/jxra',
+ 'jxrs' => 'image/jxrs',
+ 'jxs' => 'image/jxs',
+ 'jxsc' => 'image/jxsc',
+ 'jxsi' => 'image/jxsi',
+ 'jxss' => 'image/jxss',
+ 'kar' => 'audio/midi',
+ 'karbon' => 'application/vnd.kde.karbon',
+ 'kdb' => 'application/octet-stream',
+ 'kdbx' => 'application/x-keepass2',
+ 'key' => 'application/x-iwork-keynote-sffkey',
+ 'kfo' => 'application/vnd.kde.kformula',
+ 'kia' => 'application/vnd.kidspiration',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'kne' => 'application/vnd.kinar',
+ 'knp' => 'application/vnd.kinar',
+ 'kon' => 'application/vnd.kde.kontour',
+ 'kpr' => 'application/vnd.kde.kpresenter',
+ 'kpt' => 'application/vnd.kde.kpresenter',
+ 'kpxx' => 'application/vnd.ds-keypoint',
+ 'ksp' => 'application/vnd.kde.kspread',
+ 'ktr' => 'application/vnd.kahootz',
+ 'ktx' => 'image/ktx',
+ 'ktx2' => 'image/ktx2',
+ 'ktz' => 'application/vnd.kahootz',
+ 'kwd' => 'application/vnd.kde.kword',
+ 'kwt' => 'application/vnd.kde.kword',
+ 'lasxml' => 'application/vnd.las.las+xml',
+ 'latex' => 'application/x-latex',
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ 'les' => 'application/vnd.hhe.lesson-player',
+ 'less' => 'text/less',
+ 'lgr' => 'application/lgr+xml',
+ 'lha' => 'application/octet-stream',
+ 'link66' => 'application/vnd.route66.link66+xml',
+ 'list' => 'text/plain',
+ 'list3820' => 'application/vnd.ibm.modcap',
+ 'listafp' => 'application/vnd.ibm.modcap',
+ 'litcoffee' => 'text/coffeescript',
+ 'lnk' => 'application/x-ms-shortcut',
+ 'log' => 'text/plain',
+ 'lostxml' => 'application/lost+xml',
+ 'lrf' => 'application/octet-stream',
+ 'lrm' => 'application/vnd.ms-lrm',
+ 'ltf' => 'application/vnd.frogans.ltf',
+ 'lua' => 'text/x-lua',
+ 'luac' => 'application/x-lua-bytecode',
+ 'lvp' => 'audio/vnd.lucent.voice',
+ 'lwp' => 'application/vnd.lotus-wordpro',
+ 'lzh' => 'application/octet-stream',
+ 'm1v' => 'video/mpeg',
+ 'm2a' => 'audio/mpeg',
+ 'm2v' => 'video/mpeg',
+ 'm3a' => 'audio/mpeg',
+ 'm3u' => 'text/plain',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'm4a' => 'audio/x-m4a',
+ 'm4p' => 'application/mp4',
+ 'm4s' => 'video/iso.segment',
+ 'm4u' => 'application/vnd.mpegurl',
+ 'm4v' => 'video/x-m4v',
+ 'm13' => 'application/x-msmediaview',
+ 'm14' => 'application/x-msmediaview',
+ 'm21' => 'application/mp21',
+ 'ma' => 'application/mathematica',
+ 'mads' => 'application/mads+xml',
+ 'maei' => 'application/mmt-aei+xml',
+ 'mag' => 'application/vnd.ecowin.chart',
+ 'maker' => 'application/vnd.framemaker',
+ 'man' => 'text/troff',
+ 'manifest' => 'text/cache-manifest',
+ 'map' => 'application/json',
+ 'mar' => 'application/octet-stream',
+ 'markdown' => 'text/markdown',
+ 'mathml' => 'application/mathml+xml',
+ 'mb' => 'application/mathematica',
+ 'mbk' => 'application/vnd.mobius.mbk',
+ 'mbox' => 'application/mbox',
+ 'mc1' => 'application/vnd.medcalcdata',
+ 'mcd' => 'application/vnd.mcd',
+ 'mcurl' => 'text/vnd.curl.mcurl',
+ 'md' => 'text/markdown',
+ 'mdb' => 'application/x-msaccess',
+ 'mdi' => 'image/vnd.ms-modi',
+ 'mdx' => 'text/mdx',
+ 'me' => 'text/troff',
+ 'mesh' => 'model/mesh',
+ 'meta4' => 'application/metalink4+xml',
+ 'metalink' => 'application/metalink+xml',
+ 'mets' => 'application/mets+xml',
+ 'mfm' => 'application/vnd.mfmp',
+ 'mft' => 'application/rpki-manifest',
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
+ 'mgz' => 'application/vnd.proteus.magazine',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mie' => 'application/x-mie',
+ 'mif' => 'application/vnd.mif',
+ 'mime' => 'message/rfc822',
+ 'mj2' => 'video/mj2',
+ 'mjp2' => 'video/mj2',
+ 'mjs' => 'text/javascript',
+ 'mk3d' => 'video/x-matroska',
+ 'mka' => 'audio/x-matroska',
+ 'mkd' => 'text/x-markdown',
+ 'mks' => 'video/x-matroska',
+ 'mkv' => 'video/x-matroska',
+ 'mlp' => 'application/vnd.dolby.mlp',
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
+ 'mmf' => 'application/vnd.smaf',
+ 'mml' => 'text/mathml',
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
+ 'mng' => 'video/x-mng',
+ 'mny' => 'application/x-msmoney',
+ 'mobi' => 'application/x-mobipocket-ebook',
+ 'mods' => 'application/mods+xml',
+ 'mov' => 'video/quicktime',
+ 'movie' => 'video/x-sgi-movie',
+ 'mp2' => 'audio/mpeg',
+ 'mp2a' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4s' => 'application/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mp21' => 'application/mp21',
+ 'mpc' => 'application/vnd.mophun.certificate',
+ 'mpd' => 'application/dash+xml',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpf' => 'application/media-policy-dataset+xml',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'mpga' => 'audio/mpeg',
+ 'mpkg' => 'application/vnd.apple.installer+xml',
+ 'mpm' => 'application/vnd.blueice.multipass',
+ 'mpn' => 'application/vnd.mophun.application',
+ 'mpp' => 'application/vnd.ms-project',
+ 'mpt' => 'application/vnd.ms-project',
+ 'mpy' => 'application/vnd.ibm.minipay',
+ 'mqy' => 'application/vnd.mobius.mqy',
+ 'mrc' => 'application/marc',
+ 'mrcx' => 'application/marcxml+xml',
+ 'ms' => 'text/troff',
+ 'mscml' => 'application/mediaservercontrol+xml',
+ 'mseed' => 'application/vnd.fdsn.mseed',
+ 'mseq' => 'application/vnd.mseq',
+ 'msf' => 'application/vnd.epson.msf',
+ 'msg' => 'application/vnd.ms-outlook',
+ 'msh' => 'model/mesh',
+ 'msi' => 'application/x-msdownload',
+ 'msix' => 'application/msix',
+ 'msixbundle' => 'application/msixbundle',
+ 'msl' => 'application/vnd.mobius.msl',
+ 'msm' => 'application/octet-stream',
+ 'msp' => 'application/octet-stream',
+ 'msty' => 'application/vnd.muvee.style',
+ 'mtl' => 'model/mtl',
+ 'mts' => 'model/vnd.mts',
+ 'mus' => 'application/vnd.musician',
+ 'musd' => 'application/mmt-usd+xml',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ 'mvb' => 'application/x-msmediaview',
+ 'mvt' => 'application/vnd.mapbox-vector-tile',
+ 'mwf' => 'application/vnd.mfer',
+ 'mxf' => 'application/mxf',
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'mxmf' => 'audio/mobile-xmf',
+ 'mxml' => 'application/xv+xml',
+ 'mxs' => 'application/vnd.triscape.mxs',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
+ 'n3' => 'text/n3',
+ 'nb' => 'application/mathematica',
+ 'nbp' => 'application/vnd.wolfram.player',
+ 'nc' => 'application/x-netcdf',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'nfo' => 'text/x-nfo',
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
+ 'nitf' => 'application/vnd.nitf',
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
+ 'nml' => 'application/vnd.enliven',
+ 'nnd' => 'application/vnd.noblenet-directory',
+ 'nns' => 'application/vnd.noblenet-sealer',
+ 'nnw' => 'application/vnd.noblenet-web',
+ 'npx' => 'image/vnd.net-fpx',
+ 'nq' => 'application/n-quads',
+ 'nsc' => 'application/x-conference',
+ 'nsf' => 'application/vnd.lotus-notes',
+ 'nt' => 'application/n-triples',
+ 'ntf' => 'application/vnd.nitf',
+ 'numbers' => 'application/x-iwork-numbers-sffnumbers',
+ 'nzb' => 'application/x-nzb',
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
+ 'oas' => 'application/vnd.fujitsu.oasys',
+ 'obd' => 'application/x-msbinder',
+ 'obgx' => 'application/vnd.openblox.game+xml',
+ 'obj' => 'model/obj',
+ 'oda' => 'application/oda',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'oga' => 'audio/ogg',
+ 'ogex' => 'model/vnd.opengex',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'omdoc' => 'application/omdoc+xml',
+ 'onepkg' => 'application/onenote',
+ 'onetmp' => 'application/onenote',
+ 'onetoc' => 'application/onenote',
+ 'onetoc2' => 'application/onenote',
+ 'opf' => 'application/oebps-package+xml',
+ 'opml' => 'text/x-opml',
+ 'oprc' => 'application/vnd.palm',
+ 'opus' => 'audio/ogg',
+ 'org' => 'text/x-org',
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'osm' => 'application/vnd.openstreetmap.data+xml',
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
+ 'otf' => 'font/otf',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'ova' => 'application/x-virtualbox-ova',
+ 'ovf' => 'application/x-virtualbox-ovf',
+ 'owl' => 'application/rdf+xml',
+ 'oxps' => 'application/oxps',
+ 'oxt' => 'application/vnd.openofficeorg.extension',
+ 'p' => 'text/x-pascal',
+ 'p7a' => 'application/x-pkcs7-signature',
+ 'p7b' => 'application/x-pkcs7-certificates',
+ 'p7c' => 'application/pkcs7-mime',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'p7s' => 'application/pkcs7-signature',
+ 'p8' => 'application/pkcs8',
+ 'p10' => 'application/x-pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'pac' => 'application/x-ns-proxy-autoconfig',
+ 'pages' => 'application/x-iwork-pages-sffpages',
+ 'pas' => 'text/x-pascal',
+ 'paw' => 'application/vnd.pawaafile',
+ 'pbd' => 'application/vnd.powerbuilder6',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pcap' => 'application/vnd.tcpdump.pcap',
+ 'pcf' => 'application/x-font-pcf',
+ 'pcl' => 'application/vnd.hp-pcl',
+ 'pclxl' => 'application/vnd.hp-pclxl',
+ 'pct' => 'image/x-pict',
+ 'pcurl' => 'application/vnd.curl.pcurl',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/x-pilot',
+ 'pde' => 'text/x-processing',
+ 'pdf' => 'application/pdf',
+ 'pem' => 'application/x-x509-user-cert',
+ 'pfa' => 'application/x-font-type1',
+ 'pfb' => 'application/x-font-type1',
+ 'pfm' => 'application/x-font-type1',
+ 'pfr' => 'application/font-tdpfr',
+ 'pfx' => 'application/x-pkcs12',
+ 'pgm' => 'image/x-portable-graymap',
+ 'pgn' => 'application/x-chess-pgn',
+ 'pgp' => 'application/pgp',
+ 'phar' => 'application/octet-stream',
+ 'php' => 'application/x-httpd-php',
+ 'php3' => 'application/x-httpd-php',
+ 'php4' => 'application/x-httpd-php',
+ 'phps' => 'application/x-httpd-php-source',
+ 'phtml' => 'application/x-httpd-php',
+ 'pic' => 'image/x-pict',
+ 'pkg' => 'application/octet-stream',
+ 'pki' => 'application/pkixcmp',
+ 'pkipath' => 'application/pkix-pkipath',
+ 'pkpass' => 'application/vnd.apple.pkpass',
+ 'pl' => 'application/x-perl',
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
+ 'plc' => 'application/vnd.mobius.plc',
+ 'plf' => 'application/vnd.pocketlearn',
+ 'pls' => 'application/pls+xml',
+ 'pm' => 'application/x-perl',
+ 'pml' => 'application/vnd.ctc-posml',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'portpkg' => 'application/vnd.macports.portpkg',
+ 'pot' => 'application/vnd.ms-powerpoint',
+ 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppa' => 'application/vnd.ms-powerpoint',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 'ppd' => 'application/vnd.cups-ppd',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppt' => 'application/powerpoint',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'pqa' => 'application/vnd.palm',
+ 'prc' => 'model/prc',
+ 'pre' => 'application/vnd.lotus-freelance',
+ 'prf' => 'application/pics-rules',
+ 'provx' => 'application/provenance+xml',
+ 'ps' => 'application/postscript',
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
+ 'psd' => 'application/x-photoshop',
+ 'psf' => 'application/x-font-linux-psf',
+ 'pskcxml' => 'application/pskc+xml',
+ 'pti' => 'image/prs.pti',
+ 'ptid' => 'application/vnd.pvi.ptid1',
+ 'pub' => 'application/x-mspublisher',
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
+ 'pwn' => 'application/vnd.3m.post-it-notes',
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
+ 'pyo' => 'model/vnd.pytha.pyox',
+ 'pyox' => 'model/vnd.pytha.pyox',
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
+ 'qam' => 'application/vnd.epson.quickanime',
+ 'qbo' => 'application/vnd.intu.qbo',
+ 'qfx' => 'application/vnd.intu.qfx',
+ 'qps' => 'application/vnd.publishare-delta-tree',
+ 'qt' => 'video/quicktime',
+ 'qwd' => 'application/vnd.quark.quarkxpress',
+ 'qwt' => 'application/vnd.quark.quarkxpress',
+ 'qxb' => 'application/vnd.quark.quarkxpress',
+ 'qxd' => 'application/vnd.quark.quarkxpress',
+ 'qxl' => 'application/vnd.quark.quarkxpress',
+ 'qxt' => 'application/vnd.quark.quarkxpress',
+ 'ra' => 'audio/x-realaudio',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'raml' => 'application/raml+yaml',
+ 'rapd' => 'application/route-apd+xml',
+ 'rar' => 'application/x-rar',
+ 'ras' => 'image/x-cmu-raster',
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
+ 'rdf' => 'application/rdf+xml',
+ 'rdz' => 'application/vnd.data-vision.rdz',
+ 'relo' => 'application/p2p-overlay+xml',
+ 'rep' => 'application/vnd.businessobjects',
+ 'res' => 'application/x-dtbresource+xml',
+ 'rgb' => 'image/x-rgb',
+ 'rif' => 'application/reginfo+xml',
+ 'rip' => 'audio/vnd.rip',
+ 'ris' => 'application/x-research-info-systems',
+ 'rl' => 'application/resource-lists+xml',
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
+ 'rld' => 'application/resource-lists-diff+xml',
+ 'rm' => 'audio/x-pn-realaudio',
+ 'rmi' => 'audio/midi',
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
+ 'rmvb' => 'application/vnd.rn-realmedia-vbr',
+ 'rnc' => 'application/relax-ng-compact-syntax',
+ 'rng' => 'application/xml',
+ 'roa' => 'application/rpki-roa',
+ 'roff' => 'text/troff',
+ 'rp9' => 'application/vnd.cloanto.rp9',
+ 'rpm' => 'audio/x-pn-realaudio-plugin',
+ 'rpss' => 'application/vnd.nokia.radio-presets',
+ 'rpst' => 'application/vnd.nokia.radio-preset',
+ 'rq' => 'application/sparql-query',
+ 'rs' => 'application/rls-services+xml',
+ 'rsa' => 'application/x-pkcs7',
+ 'rsat' => 'application/atsc-rsat+xml',
+ 'rsd' => 'application/rsd+xml',
+ 'rsheet' => 'application/urc-ressheet+xml',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'text/rtf',
+ 'rtx' => 'text/richtext',
+ 'run' => 'application/x-makeself',
+ 'rusd' => 'application/route-usd+xml',
+ 'rv' => 'video/vnd.rn-realvideo',
+ 's' => 'text/x-asm',
+ 's3m' => 'audio/s3m',
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
+ 'sass' => 'text/x-sass',
+ 'sbml' => 'application/sbml+xml',
+ 'sc' => 'application/vnd.ibm.secure-container',
+ 'scd' => 'application/x-msschedule',
+ 'scm' => 'application/vnd.lotus-screencam',
+ 'scq' => 'application/scvp-cv-request',
+ 'scs' => 'application/scvp-cv-response',
+ 'scss' => 'text/x-scss',
+ 'scurl' => 'text/vnd.curl.scurl',
+ 'sda' => 'application/vnd.stardivision.draw',
+ 'sdc' => 'application/vnd.stardivision.calc',
+ 'sdd' => 'application/vnd.stardivision.impress',
+ 'sdkd' => 'application/vnd.solent.sdkm+xml',
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
+ 'sdp' => 'application/sdp',
+ 'sdw' => 'application/vnd.stardivision.writer',
+ 'sea' => 'application/octet-stream',
+ 'see' => 'application/vnd.seemail',
+ 'seed' => 'application/vnd.fdsn.seed',
+ 'sema' => 'application/vnd.sema',
+ 'semd' => 'application/vnd.semd',
+ 'semf' => 'application/vnd.semf',
+ 'senmlx' => 'application/senml+xml',
+ 'sensmlx' => 'application/sensml+xml',
+ 'ser' => 'application/java-serialized-object',
+ 'setpay' => 'application/set-payment-initiation',
+ 'setreg' => 'application/set-registration-initiation',
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
+ 'sfs' => 'application/vnd.spotfire.sfs',
+ 'sfv' => 'text/x-sfv',
+ 'sgi' => 'image/sgi',
+ 'sgl' => 'application/vnd.stardivision.writer-global',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'shex' => 'text/shex',
+ 'shf' => 'application/shf+xml',
+ 'shtml' => 'text/html',
+ 'sid' => 'image/x-mrsid-image',
+ 'sieve' => 'application/sieve',
+ 'sig' => 'application/pgp-signature',
+ 'sil' => 'audio/silk',
+ 'silo' => 'model/mesh',
+ 'sis' => 'application/vnd.symbian.install',
+ 'sisx' => 'application/vnd.symbian.install',
+ 'sit' => 'application/x-stuffit',
+ 'sitx' => 'application/x-stuffitx',
+ 'siv' => 'application/sieve',
+ 'skd' => 'application/vnd.koan',
+ 'skm' => 'application/vnd.koan',
+ 'skp' => 'application/vnd.koan',
+ 'skt' => 'application/vnd.koan',
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'slim' => 'text/slim',
+ 'slm' => 'text/slim',
+ 'sls' => 'application/route-s-tsid+xml',
+ 'slt' => 'application/vnd.epson.salt',
+ 'sm' => 'application/vnd.stepmania.stepchart',
+ 'smf' => 'application/vnd.stardivision.math',
+ 'smi' => 'application/smil',
+ 'smil' => 'application/smil',
+ 'smv' => 'video/x-smv',
+ 'smzip' => 'application/vnd.stepmania.package',
+ 'snd' => 'audio/basic',
+ 'snf' => 'application/x-font-snf',
+ 'so' => 'application/octet-stream',
+ 'spc' => 'application/x-pkcs7-certificates',
+ 'spdx' => 'text/spdx',
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
+ 'spl' => 'application/x-futuresplash',
+ 'spot' => 'text/vnd.in3d.spot',
+ 'spp' => 'application/scvp-vp-response',
+ 'spq' => 'application/scvp-vp-request',
+ 'spx' => 'audio/ogg',
+ 'sql' => 'application/x-sql',
+ 'src' => 'application/x-wais-source',
+ 'srt' => 'application/x-subrip',
+ 'sru' => 'application/sru+xml',
+ 'srx' => 'application/sparql-results+xml',
+ 'ssdl' => 'application/ssdl+xml',
+ 'sse' => 'application/vnd.kodak-descriptor',
+ 'ssf' => 'application/vnd.epson.ssf',
+ 'ssml' => 'application/ssml+xml',
+ 'sst' => 'application/octet-stream',
+ 'st' => 'application/vnd.sailingtracker.track',
+ 'stc' => 'application/vnd.sun.xml.calc.template',
+ 'std' => 'application/vnd.sun.xml.draw.template',
+ 'step' => 'application/STEP',
+ 'stf' => 'application/vnd.wt.stf',
+ 'sti' => 'application/vnd.sun.xml.impress.template',
+ 'stk' => 'application/hyperstudio',
+ 'stl' => 'model/stl',
+ 'stp' => 'application/STEP',
+ 'stpx' => 'model/step+xml',
+ 'stpxz' => 'model/step-xml+zip',
+ 'stpz' => 'model/step+zip',
+ 'str' => 'application/vnd.pg.format',
+ 'stw' => 'application/vnd.sun.xml.writer.template',
+ 'styl' => 'text/stylus',
+ 'stylus' => 'text/stylus',
+ 'sub' => 'text/vnd.dvb.subtitle',
+ 'sus' => 'application/vnd.sus-calendar',
+ 'susp' => 'application/vnd.sus-calendar',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 'svc' => 'application/vnd.dvb.service',
+ 'svd' => 'application/vnd.svd',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg+xml',
+ 'swa' => 'application/x-director',
+ 'swf' => 'application/x-shockwave-flash',
+ 'swi' => 'application/vnd.aristanetworks.swi',
+ 'swidtag' => 'application/swid+xml',
+ 'sxc' => 'application/vnd.sun.xml.calc',
+ 'sxd' => 'application/vnd.sun.xml.draw',
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
+ 'sxi' => 'application/vnd.sun.xml.impress',
+ 'sxm' => 'application/vnd.sun.xml.math',
+ 'sxw' => 'application/vnd.sun.xml.writer',
+ 't' => 'text/troff',
+ 't3' => 'application/x-t3vm-image',
+ 't38' => 'image/t38',
+ 'taglet' => 'application/vnd.mynfc',
+ 'tao' => 'application/vnd.tao.intent-module-archive',
+ 'tap' => 'image/vnd.tencent.tap',
+ 'tar' => 'application/x-tar',
+ 'tcap' => 'application/vnd.3gpp2.tcap',
+ 'tcl' => 'application/x-tcl',
+ 'td' => 'application/urc-targetdesc+xml',
+ 'teacher' => 'application/vnd.smart.teacher',
+ 'tei' => 'application/tei+xml',
+ 'teicorpus' => 'application/tei+xml',
+ 'tex' => 'application/x-tex',
+ 'texi' => 'application/x-texinfo',
+ 'texinfo' => 'application/x-texinfo',
+ 'text' => 'text/plain',
+ 'tfi' => 'application/thraud+xml',
+ 'tfm' => 'application/x-tex-tfm',
+ 'tfx' => 'image/tiff-fx',
+ 'tga' => 'image/x-tga',
+ 'tgz' => 'application/x-tar',
+ 'thmx' => 'application/vnd.ms-officetheme',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'tk' => 'application/x-tcl',
+ 'tmo' => 'application/vnd.tmobile-livetv',
+ 'toml' => 'application/toml',
+ 'torrent' => 'application/x-bittorrent',
+ 'tpl' => 'application/vnd.groove-tool-template',
+ 'tpt' => 'application/vnd.trid.tpt',
+ 'tr' => 'text/troff',
+ 'tra' => 'application/vnd.trueapp',
+ 'trig' => 'application/trig',
+ 'trm' => 'application/x-msterminal',
+ 'ts' => 'video/mp2t',
+ 'tsd' => 'application/timestamped-data',
+ 'tsv' => 'text/tab-separated-values',
+ 'ttc' => 'font/collection',
+ 'ttf' => 'font/ttf',
+ 'ttl' => 'text/turtle',
+ 'ttml' => 'application/ttml+xml',
+ 'twd' => 'application/vnd.simtech-mindmapper',
+ 'twds' => 'application/vnd.simtech-mindmapper',
+ 'txd' => 'application/vnd.genomatix.tuxedo',
+ 'txf' => 'application/vnd.mobius.txf',
+ 'txt' => 'text/plain',
+ 'u3d' => 'model/u3d',
+ 'u8dsn' => 'message/global-delivery-status',
+ 'u8hdr' => 'message/global-headers',
+ 'u8mdn' => 'message/global-disposition-notification',
+ 'u8msg' => 'message/global',
+ 'u32' => 'application/x-authorware-bin',
+ 'ubj' => 'application/ubjson',
+ 'udeb' => 'application/x-debian-package',
+ 'ufd' => 'application/vnd.ufdl',
+ 'ufdl' => 'application/vnd.ufdl',
+ 'ulx' => 'application/x-glulx',
+ 'umj' => 'application/vnd.umajin',
+ 'unityweb' => 'application/vnd.unity',
+ 'uo' => 'application/vnd.uoml+xml',
+ 'uoml' => 'application/vnd.uoml+xml',
+ 'uri' => 'text/uri-list',
+ 'uris' => 'text/uri-list',
+ 'urls' => 'text/uri-list',
+ 'usda' => 'model/vnd.usda',
+ 'usdz' => 'model/vnd.usdz+zip',
+ 'ustar' => 'application/x-ustar',
+ 'utz' => 'application/vnd.uiq.theme',
+ 'uu' => 'text/x-uuencode',
+ 'uva' => 'audio/vnd.dece.audio',
+ 'uvd' => 'application/vnd.dece.data',
+ 'uvf' => 'application/vnd.dece.data',
+ 'uvg' => 'image/vnd.dece.graphic',
+ 'uvh' => 'video/vnd.dece.hd',
+ 'uvi' => 'image/vnd.dece.graphic',
+ 'uvm' => 'video/vnd.dece.mobile',
+ 'uvp' => 'video/vnd.dece.pd',
+ 'uvs' => 'video/vnd.dece.sd',
+ 'uvt' => 'application/vnd.dece.ttml+xml',
+ 'uvu' => 'video/vnd.uvvu.mp4',
+ 'uvv' => 'video/vnd.dece.video',
+ 'uvva' => 'audio/vnd.dece.audio',
+ 'uvvd' => 'application/vnd.dece.data',
+ 'uvvf' => 'application/vnd.dece.data',
+ 'uvvg' => 'image/vnd.dece.graphic',
+ 'uvvh' => 'video/vnd.dece.hd',
+ 'uvvi' => 'image/vnd.dece.graphic',
+ 'uvvm' => 'video/vnd.dece.mobile',
+ 'uvvp' => 'video/vnd.dece.pd',
+ 'uvvs' => 'video/vnd.dece.sd',
+ 'uvvt' => 'application/vnd.dece.ttml+xml',
+ 'uvvu' => 'video/vnd.uvvu.mp4',
+ 'uvvv' => 'video/vnd.dece.video',
+ 'uvvx' => 'application/vnd.dece.unspecified',
+ 'uvvz' => 'application/vnd.dece.zip',
+ 'uvx' => 'application/vnd.dece.unspecified',
+ 'uvz' => 'application/vnd.dece.zip',
+ 'vbox' => 'application/x-virtualbox-vbox',
+ 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack',
+ 'vcard' => 'text/vcard',
+ 'vcd' => 'application/x-cdlink',
+ 'vcf' => 'text/x-vcard',
+ 'vcg' => 'application/vnd.groove-vcard',
+ 'vcs' => 'text/x-vcalendar',
+ 'vcx' => 'application/vnd.vcx',
+ 'vdi' => 'application/x-virtualbox-vdi',
+ 'vds' => 'model/vnd.sap.vds',
+ 'vhd' => 'application/x-virtualbox-vhd',
+ 'vis' => 'application/vnd.visionary',
+ 'viv' => 'video/vnd.vivo',
+ 'vlc' => 'application/videolan',
+ 'vmdk' => 'application/x-virtualbox-vmdk',
+ 'vob' => 'video/x-ms-vob',
+ 'vor' => 'application/vnd.stardivision.writer',
+ 'vox' => 'application/x-authorware-bin',
+ 'vrml' => 'model/vrml',
+ 'vsd' => 'application/vnd.visio',
+ 'vsf' => 'application/vnd.vsf',
+ 'vss' => 'application/vnd.visio',
+ 'vst' => 'application/vnd.visio',
+ 'vsw' => 'application/vnd.visio',
+ 'vtf' => 'image/vnd.valve.source.texture',
+ 'vtt' => 'text/vtt',
+ 'vtu' => 'model/vnd.vtu',
+ 'vxml' => 'application/voicexml+xml',
+ 'w3d' => 'application/x-director',
+ 'wad' => 'application/x-doom',
+ 'wadl' => 'application/vnd.sun.wadl+xml',
+ 'war' => 'application/java-archive',
+ 'wasm' => 'application/wasm',
+ 'wav' => 'audio/x-wav',
+ 'wax' => 'audio/x-ms-wax',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
+ 'wbxml' => 'application/wbxml',
+ 'wcm' => 'application/vnd.ms-works',
+ 'wdb' => 'application/vnd.ms-works',
+ 'wdp' => 'image/vnd.ms-photo',
+ 'weba' => 'audio/webm',
+ 'webapp' => 'application/x-web-app-manifest+json',
+ 'webm' => 'video/webm',
+ 'webmanifest' => 'application/manifest+json',
+ 'webp' => 'image/webp',
+ 'wg' => 'application/vnd.pmi.widget',
+ 'wgsl' => 'text/wgsl',
+ 'wgt' => 'application/widget',
+ 'wif' => 'application/watcherinfo+xml',
+ 'wks' => 'application/vnd.ms-works',
+ 'wm' => 'video/x-ms-wm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmd' => 'application/x-ms-wmd',
+ 'wmf' => 'image/wmf',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmlc' => 'application/wmlc',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wmx' => 'video/x-ms-wmx',
+ 'wmz' => 'application/x-msmetafile',
+ 'woff' => 'font/woff',
+ 'woff2' => 'font/woff2',
+ 'word' => 'application/msword',
+ 'wpd' => 'application/vnd.wordperfect',
+ 'wpl' => 'application/vnd.ms-wpl',
+ 'wps' => 'application/vnd.ms-works',
+ 'wqd' => 'application/vnd.wqd',
+ 'wri' => 'application/x-mswrite',
+ 'wrl' => 'model/vrml',
+ 'wsc' => 'message/vnd.wfa.wsc',
+ 'wsdl' => 'application/wsdl+xml',
+ 'wspolicy' => 'application/wspolicy+xml',
+ 'wtb' => 'application/vnd.webturbo',
+ 'wvx' => 'video/x-ms-wvx',
+ 'x3d' => 'model/x3d+xml',
+ 'x3db' => 'model/x3d+fastinfoset',
+ 'x3dbz' => 'model/x3d+binary',
+ 'x3dv' => 'model/x3d-vrml',
+ 'x3dvz' => 'model/x3d+vrml',
+ 'x3dz' => 'model/x3d+xml',
+ 'x32' => 'application/x-authorware-bin',
+ 'x_b' => 'model/vnd.parasolid.transmit.binary',
+ 'x_t' => 'model/vnd.parasolid.transmit.text',
+ 'xaml' => 'application/xaml+xml',
+ 'xap' => 'application/x-silverlight-app',
+ 'xar' => 'application/vnd.xara',
+ 'xav' => 'application/xcap-att+xml',
+ 'xbap' => 'application/x-ms-xbap',
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
+ 'xbm' => 'image/x-xbitmap',
+ 'xca' => 'application/xcap-caps+xml',
+ 'xcs' => 'application/calendar+xml',
+ 'xdf' => 'application/xcap-diff+xml',
+ 'xdm' => 'application/vnd.syncml.dm+xml',
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
+ 'xdssc' => 'application/dssc+xml',
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
+ 'xel' => 'application/xcap-el+xml',
+ 'xenc' => 'application/xenc+xml',
+ 'xer' => 'application/patch-ops-error+xml',
+ 'xfdf' => 'application/xfdf',
+ 'xfdl' => 'application/vnd.xfdl',
+ 'xht' => 'application/xhtml+xml',
+ 'xhtm' => 'application/vnd.pwg-xhtml-print+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xhvml' => 'application/xv+xml',
+ 'xif' => 'image/vnd.xiff',
+ 'xl' => 'application/excel',
+ 'xla' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'xlc' => 'application/vnd.ms-excel',
+ 'xlf' => 'application/xliff+xml',
+ 'xlm' => 'application/vnd.ms-excel',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlw' => 'application/vnd.ms-excel',
+ 'xm' => 'audio/xm',
+ 'xml' => 'application/xml',
+ 'xns' => 'application/xcap-ns+xml',
+ 'xo' => 'application/vnd.olpc-sugar',
+ 'xop' => 'application/xop+xml',
+ 'xpi' => 'application/x-xpinstall',
+ 'xpl' => 'application/xproc+xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xpr' => 'application/vnd.is-xpr',
+ 'xps' => 'application/vnd.ms-xpsdocument',
+ 'xpw' => 'application/vnd.intercon.formnet',
+ 'xpx' => 'application/vnd.intercon.formnet',
+ 'xsd' => 'application/xml',
+ 'xsf' => 'application/prs.xsf+xml',
+ 'xsl' => 'application/xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xsm' => 'application/vnd.syncml+xml',
+ 'xspf' => 'application/xspf+xml',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xvm' => 'application/xv+xml',
+ 'xvml' => 'application/xv+xml',
+ 'xwd' => 'image/x-xwindowdump',
+ 'xyz' => 'chemical/x-xyz',
+ 'xz' => 'application/x-xz',
+ 'yaml' => 'text/yaml',
+ 'yang' => 'application/yang',
+ 'yin' => 'application/yin+xml',
+ 'yml' => 'text/yaml',
+ 'ymp' => 'text/x-suse-ymp',
+ 'z' => 'application/x-compress',
+ 'z1' => 'application/x-zmachine',
+ 'z2' => 'application/x-zmachine',
+ 'z3' => 'application/x-zmachine',
+ 'z4' => 'application/x-zmachine',
+ 'z5' => 'application/x-zmachine',
+ 'z6' => 'application/x-zmachine',
+ 'z7' => 'application/x-zmachine',
+ 'z8' => 'application/x-zmachine',
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
+ 'zip' => 'application/zip',
+ 'zir' => 'application/vnd.zul',
+ 'zirz' => 'application/vnd.zul',
+ 'zmm' => 'application/vnd.handheld-entertainment+xml',
+ 'zsh' => 'text/x-scriptzsh',
+ ];
+
+ /**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
+ */
+ public static function fromFilename(string $filename): ?string
+ {
+ return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION));
+ }
+
+ /**
+ * Maps a file extensions to a mimetype.
+ *
+ * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
+ */
+ public static function fromExtension(string $extension): ?string
+ {
+ return self::MIME_TYPES[strtolower($extension)] ?? null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100644
index 0000000..43d718f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,165 @@
+boundary = $boundary ?: bin2hex(random_bytes(20));
+ $this->stream = $this->createStream($elements);
+ }
+
+ public function getBoundary(): string
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ *
+ * @param string[] $headers
+ */
+ private function getHeaders(array $headers): string
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements = []): StreamInterface
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ if (!is_array($element)) {
+ throw new \UnexpectedValueException('An array is expected');
+ }
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element): void
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = Utils::streamFor($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ [$body, $headers] = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ $element['filename'] ?? null,
+ $element['headers'] ?? []
+ );
+
+ $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(Utils::streamFor("\r\n"));
+ }
+
+ /**
+ * @param string[] $headers
+ *
+ * @return array{0: StreamInterface, 1: string[]}
+ */
+ private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = self::getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf(
+ 'form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename)
+ )
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = self::getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = self::getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * @param string[] $headers
+ */
+ private static function getHeader(array $headers, string $key): ?string
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower((string) $k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100644
index 0000000..161a224
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,28 @@
+source = $source;
+ $this->size = $options['size'] ?? null;
+ $this->metadata = $options['metadata'] ?? [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString(): string
+ {
+ try {
+ return Utils::copyToString($this);
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function close(): void
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = 0;
+ $this->source = null;
+
+ return null;
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->size;
+ }
+
+ public function tell(): int
+ {
+ return $this->tellPos;
+ }
+
+ public function eof(): bool
+ {
+ return $this->source === null;
+ }
+
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write($string): int
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read($length): string
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents(): string
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return $this->metadata[$key] ?? null;
+ }
+
+ private function pump(int $length): void
+ {
+ if ($this->source !== null) {
+ do {
+ $data = ($this->source)($length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php
new file mode 100644
index 0000000..ccf867a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Query.php
@@ -0,0 +1,118 @@
+ '1', 'foo[b]' => '2'])`.
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ */
+ public static function parse(string $str, $urlEncoding = true): array
+ {
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', (string) $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) {
+ return $str;
+ };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!array_key_exists($key, $result)) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of `parse()` to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like `http_build_query()` would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode,
+ * PHP_QUERY_RFC3986 to encode using
+ * RFC3986, or PHP_QUERY_RFC1738 to
+ * encode using RFC1738.
+ * @param bool $treatBoolsAsInts Set to true to encode as 0/1, and
+ * false as false/true.
+ */
+ public static function build(array $params, $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string
+ {
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function (string $str): string {
+ return $str;
+ };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $castBool = $treatBoolsAsInts ? static function ($v) { return (int) $v; } : static function ($v) { return $v ? 'true' : 'false'; };
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder((string) $k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ $v = is_bool($v) ? $castBool($v) : $v;
+ if ($v !== null) {
+ $qs .= '='.$encoder((string) $v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ $vv = is_bool($vv) ? $castBool($vv) : $vv;
+ if ($vv !== null) {
+ $qs .= '='.$encoder((string) $vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100644
index 0000000..faafe1a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,159 @@
+assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = Utils::streamFor($body);
+ }
+ }
+
+ public function getRequestTarget(): string
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target === '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?'.$this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget): RequestInterface
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+
+ return $new;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method): RequestInterface
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+
+ return $new;
+ }
+
+ public function getUri(): UriInterface
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri(): void
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':'.$port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ /**
+ * @param mixed $method
+ */
+ private function assertMethod($method): void
+ {
+ if (!is_string($method) || $method === '') {
+ throw new InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100644
index 0000000..34e612f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,161 @@
+ 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase;
+
+ /** @var int */
+ private $statusCode;
+
+ /**
+ * @param int $status Status code
+ * @param (string|string[])[] $headers Response headers
+ * @param string|resource|StreamInterface|null $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ int $status = 200,
+ array $headers = [],
+ $body = null,
+ string $version = '1.1',
+ ?string $reason = null
+ ) {
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = Utils::streamFor($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::PHRASES[$this->statusCode])) {
+ $this->reasonPhrase = self::PHRASES[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase(): string
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = ''): ResponseInterface
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) {
+ $reasonPhrase = self::PHRASES[$new->statusCode];
+ }
+ $new->reasonPhrase = (string) $reasonPhrase;
+
+ return $new;
+ }
+
+ /**
+ * @param mixed $statusCode
+ */
+ private function assertStatusCodeIsInteger($statusCode): void
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange(int $statusCode): void
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100644
index 0000000..8219dba
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,23 @@
+@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100644
index 0000000..3cc9534
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,340 @@
+serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files An array which respect $_FILES structure
+ *
+ * @throws InvalidArgumentException for unrecognized values
+ */
+ public static function normalizeFiles(array $files): array
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ *
+ * @return UploadedFileInterface|UploadedFileInterface[]
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = []): array
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key] ?? null,
+ 'error' => $files['error'][$key] ?? null,
+ 'name' => $files['name'][$key] ?? null,
+ 'type' => $files['type'][$key] ?? null,
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ */
+ public static function fromGlobals(): ServerRequestInterface
+ {
+ $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority(string $authority): array
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = $parts['host'] ?? null;
+ $port = $parts['port'] ?? null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ */
+ public static function getUriFromGlobals(): UriInterface
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+ public function getServerParams(): array
+ {
+ return $this->serverParams;
+ }
+
+ public function getUploadedFiles(): array
+ {
+ return $this->uploadedFiles;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ public function getCookieParams(): array
+ {
+ return $this->cookieParams;
+ }
+
+ public function withCookieParams(array $cookies): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ public function getQueryParams(): array
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * @return array|object|null
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ public function withAttribute($attribute, $value): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ public function withoutAttribute($attribute): ServerRequestInterface
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100644
index 0000000..0aff9b2
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,283 @@
+size = $options['size'];
+ }
+
+ $this->customMetadata = $options['metadata'] ?? [];
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString(): string
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function getContents(): string
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+
+ return Utils::tryGetContents($this->stream);
+ }
+
+ public function close(): void
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize(): ?int
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (is_array($stats) && isset($stats['size'])) {
+ $this->size = $stats['size'];
+
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable(): bool
+ {
+ return $this->readable;
+ }
+
+ public function isWritable(): bool
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->seekable;
+ }
+
+ public function eof(): bool
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell(): int
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ .$offset.' with whence '.var_export($whence, true));
+ }
+ }
+
+ public function read($length): string
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ try {
+ $string = fread($this->stream, $length);
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to read from stream', 0, $e);
+ }
+
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string): int
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return $meta[$key] ?? null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100644
index 0000000..601c13a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,156 @@
+stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @return StreamInterface
+ */
+ public function __get(string $name)
+ {
+ if ($name === 'stream') {
+ $this->stream = $this->createStream();
+
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString(): string
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function getContents(): string
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @return mixed
+ */
+ public function __call(string $method, array $args)
+ {
+ /** @var callable $callable */
+ $callable = [$this->stream, $method];
+ $result = ($callable)(...$args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close(): void
+ {
+ $this->stream->close();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof(): bool
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell(): int
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable(): bool
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable(): bool
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length): string
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string): int
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @throws \BadMethodCallException
+ */
+ protected function createStream(): StreamInterface
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100644
index 0000000..77b04d7
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,207 @@
+isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ .'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream],
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register(): void
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read(int $count): string
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write(string $data): int
+ {
+ return $this->stream->write($data);
+ }
+
+ public function stream_tell(): int
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof(): bool
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek(int $offset, int $whence): bool
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ /**
+ * @return resource|false
+ */
+ public function stream_cast(int $cast_as)
+ {
+ $stream = clone $this->stream;
+ $resource = $stream->detach();
+
+ return $resource ?? false;
+ }
+
+ /**
+ * @return array{
+ * dev: int,
+ * ino: int,
+ * mode: int,
+ * nlink: int,
+ * uid: int,
+ * gid: int,
+ * rdev: int,
+ * size: int,
+ * atime: int,
+ * mtime: int,
+ * ctime: int,
+ * blksize: int,
+ * blocks: int
+ * }|false
+ */
+ public function stream_stat()
+ {
+ if ($this->stream->getSize() === null) {
+ return false;
+ }
+
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188,
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ /**
+ * @return array{
+ * dev: int,
+ * ino: int,
+ * mode: int,
+ * nlink: int,
+ * uid: int,
+ * gid: int,
+ * rdev: int,
+ * size: int,
+ * atime: int,
+ * mtime: int,
+ * ctime: int,
+ * blksize: int,
+ * blocks: int
+ * }
+ */
+ public function url_stat(string $path, int $flags): array
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100644
index 0000000..d9b779f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,211 @@
+ 'UPLOAD_ERR_OK',
+ UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE',
+ UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE',
+ UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL',
+ UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE',
+ UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR',
+ UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE',
+ UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION',
+ ];
+
+ /**
+ * @var string|null
+ */
+ private $clientFilename;
+
+ /**
+ * @var string|null
+ */
+ private $clientMediaType;
+
+ /**
+ * @var int
+ */
+ private $error;
+
+ /**
+ * @var string|null
+ */
+ private $file;
+
+ /**
+ * @var bool
+ */
+ private $moved = false;
+
+ /**
+ * @var int|null
+ */
+ private $size;
+
+ /**
+ * @var StreamInterface|null
+ */
+ private $stream;
+
+ /**
+ * @param StreamInterface|string|resource $streamOrFile
+ */
+ public function __construct(
+ $streamOrFile,
+ ?int $size,
+ int $errorStatus,
+ ?string $clientFilename = null,
+ ?string $clientMediaType = null
+ ) {
+ $this->setError($errorStatus);
+ $this->size = $size;
+ $this->clientFilename = $clientFilename;
+ $this->clientMediaType = $clientMediaType;
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param StreamInterface|string|resource $streamOrFile
+ *
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile): void
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ private function setError(int $error): void
+ {
+ if (!isset(UploadedFile::ERROR_MAP[$error])) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ private static function isStringNotEmpty($param): bool
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * Return true if there is no upload error
+ */
+ private function isOk(): bool
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ public function isMoved(): bool
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive(): void
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException(\sprintf('Cannot retrieve stream due to upload error (%s)', self::ERROR_MAP[$this->error]));
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ public function getStream(): StreamInterface
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ /** @var string $file */
+ $file = $this->file;
+
+ return new LazyOpenStream($file, 'r+');
+ }
+
+ public function moveTo($targetPath): void
+ {
+ $this->validateActive();
+
+ if (false === self::isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = PHP_SAPI === 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ Utils::copyToStream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->size;
+ }
+
+ public function getError(): int
+ {
+ return $this->error;
+ }
+
+ public function getClientFilename(): ?string
+ {
+ return $this->clientFilename;
+ }
+
+ public function getClientMediaType(): ?string
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100644
index 0000000..a7cdfb0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,743 @@
+ 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ /**
+ * Unreserved characters for use in a regex.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
+ */
+ private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
+
+ /**
+ * Sub-delims for use in a regex.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+ */
+ private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
+ private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /** @var string|null String representation */
+ private $composedComponents;
+
+ public function __construct(string $uri = '')
+ {
+ if ($uri !== '') {
+ $parts = self::parse($uri);
+ if ($parts === false) {
+ throw new MalformedUriException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ /**
+ * UTF-8 aware \parse_url() replacement.
+ *
+ * The internal function produces broken output for non ASCII domain names
+ * (IDN) when used with locales other than "C".
+ *
+ * On the other hand, cURL understands IDN correctly only when UTF-8 locale
+ * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
+ *
+ * @see https://bugs.php.net/bug.php?id=52923
+ * @see https://www.php.net/manual/en/function.parse-url.php#114817
+ * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
+ *
+ * @return array|false
+ */
+ private static function parse(string $url)
+ {
+ // If IPv6
+ $prefix = '';
+ if (preg_match('%^(.*://\[[0-9:a-fA-F]+\])(.*?)$%', $url, $matches)) {
+ /** @var array{0:string, 1:string, 2:string} $matches */
+ $prefix = $matches[1];
+ $url = $matches[2];
+ }
+
+ /** @var string */
+ $encodedUrl = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ static function ($matches) {
+ return urlencode($matches[0]);
+ },
+ $url
+ );
+
+ $result = parse_url($prefix.$encodedUrl);
+
+ if ($result === false) {
+ return false;
+ }
+
+ return array_map('urldecode', $result);
+ }
+
+ public function __toString(): string
+ {
+ if ($this->composedComponents === null) {
+ $this->composedComponents = self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ return $this->composedComponents;
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
+ */
+ public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme.':';
+ }
+
+ if ($authority != '' || $scheme === 'file') {
+ $uri .= '//'.$authority;
+ }
+
+ if ($authority != '' && $path != '' && $path[0] != '/') {
+ $path = '/'.$path;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?'.$query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#'.$fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ */
+ public static function isDefaultPort(UriInterface $uri): bool
+ {
+ return $uri->getPort() === null
+ || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri): bool
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ */
+ public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ */
+ public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param (string|null)[] $keyValueArray Associative array of key and values
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @see https://www.php.net/manual/en/function.parse-url.php
+ *
+ * @throws MalformedUriException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts): UriInterface
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority(): string
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo.'@'.$authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':'.$this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo(): string
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ public function getPort(): ?int
+ {
+ return $this->port;
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function getQuery(): string
+ {
+ return $this->query;
+ }
+
+ public function getFragment(): string
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme): UriInterface
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->composedComponents = null;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null): UriInterface
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':'.$this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host): UriInterface
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port): UriInterface
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->composedComponents = null;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path): UriInterface
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query): UriInterface
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+ $new->composedComponents = null;
+
+ return $new;
+ }
+
+ public function withFragment($fragment): UriInterface
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+ $new->composedComponents = null;
+
+ return $new;
+ }
+
+ public function jsonSerialize(): string
+ {
+ return $this->__toString();
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts): void
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param mixed $scheme
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme): string
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ }
+
+ /**
+ * @param mixed $component
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component): string
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param mixed $host
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host): string
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ }
+
+ /**
+ * @param mixed $port
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port): ?int
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xFFFF < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param (string|int)[] $keys
+ *
+ * @return string[]
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys): array
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map(function ($k): string {
+ return rawurldecode((string) $k);
+ }, $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ private static function generateQueryString(string $key, ?string $value): string
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
+
+ if ($value !== null) {
+ $queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort(): void
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param mixed $path
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path): string
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param mixed $str
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str): string
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match): string
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState(): void
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php
new file mode 100644
index 0000000..70c582a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriComparator.php
@@ -0,0 +1,52 @@
+getHost(), $modified->getHost()) !== 0) {
+ return true;
+ }
+
+ if ($original->getScheme() !== $modified->getScheme()) {
+ return true;
+ }
+
+ if (self::computePort($original) !== self::computePort($modified)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static function computePort(UriInterface $uri): int
+ {
+ $port = $uri->getPort();
+
+ if (null !== $port) {
+ return $port;
+ }
+
+ return 'https' === $uri->getScheme() ? 443 : 80;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100644
index 0000000..e174557
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,220 @@
+getPath() === ''
+ && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri): UriInterface
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match): string {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match): string {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100644
index 0000000..3737be1
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,211 @@
+getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/'.$rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ */
+ public static function relativize(UriInterface $base, UriInterface $target): UriInterface
+ {
+ if ($target->getScheme() !== ''
+ && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ /** @var string $lastSegment */
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target): string
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php
new file mode 100644
index 0000000..5451e3d
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Utils.php
@@ -0,0 +1,477 @@
+ $v) {
+ if (!in_array(strtolower((string) $k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
+ {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
+ {
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ if ($buf === '') {
+ break;
+ }
+ $buffer .= $buf;
+ }
+
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ if ($buf === '') {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Calculate a hash of a stream.
+ *
+ * This method reads the entire stream to calculate a rolling hash, based
+ * on PHP's `hash_init` functions.
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
+ {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+ }
+
+ /**
+ * Clone and modify a request with the given changes.
+ *
+ * This method is useful for reducing the number of clones needed to mutate
+ * a message.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ */
+ public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
+ {
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = self::caselessRemove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ $new = (new ServerRequest(
+ $changes['method'] ?? $request->getMethod(),
+ $uri,
+ $headers,
+ $changes['body'] ?? $request->getBody(),
+ $changes['version'] ?? $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+
+ foreach ($request->getAttributes() as $key => $value) {
+ $new = $new->withAttribute($key, $value);
+ }
+
+ return $new;
+ }
+
+ return new Request(
+ $changes['method'] ?? $request->getMethod(),
+ $uri,
+ $headers,
+ $changes['body'] ?? $request->getBody(),
+ $changes['version'] ?? $request->getProtocolVersion()
+ );
+ }
+
+ /**
+ * Read a line from the stream up to the maximum allowed buffer length.
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int|null $maxLength Maximum buffer length
+ */
+ public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
+ {
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ if ('' === ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Redact the password in the user info part of a URI.
+ */
+ public static function redactUserInfo(UriInterface $uri): UriInterface
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = \strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * This method accepts the following `$resource` types:
+ * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+ * - `string`: Creates a stream object that uses the given string as the contents.
+ * - `resource`: Creates a stream object that wraps the given PHP stream resource.
+ * - `Iterator`: If the provided value implements `Iterator`, then a read-only
+ * stream object will be created that wraps the given iterable. Each time the
+ * stream is read from, data from the iterator will fill a buffer and will be
+ * continuously called until the buffer is equal to the requested read size.
+ * Subsequent read calls will first read from the buffer and then call `next`
+ * on the underlying iterator until it is exhausted.
+ * - `object` with `__toString()`: If the object has the `__toString()` method,
+ * the object will be cast to a string and then a stream will be returned that
+ * uses the string value.
+ * - `NULL`: When `null` is passed, an empty stream object is returned.
+ * - `callable` When a callable is passed, a read-only stream object will be
+ * created that invokes the given callable. The callable is invoked with the
+ * number of suggested bytes to read. The callable can return any number of
+ * bytes, but MUST return `false` when there is no more data to return. The
+ * stream object that wraps the callable will invoke the callable until the
+ * number of requested bytes are available. Any additional bytes will be
+ * buffered and used in subsequent reads.
+ *
+ * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
+ * @param array{size?: int, metadata?: array} $options Additional options
+ *
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+ public static function streamFor($resource = '', array $options = []): StreamInterface
+ {
+ if (is_scalar($resource)) {
+ $stream = self::tryFopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, (string) $resource);
+ fseek($stream, 0);
+ }
+
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ /*
+ * The 'php://input' is a special stream with quirks and inconsistencies.
+ * We avoid using that stream by reading it into php://temp
+ */
+
+ /** @var resource $resource */
+ if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') {
+ $stream = self::tryFopen('php://temp', 'w+');
+ stream_copy_to_stream($resource, $stream);
+ fseek($stream, 0);
+ $resource = $stream;
+ }
+
+ return new Stream($resource, $options);
+ case 'object':
+ /** @var object $resource */
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return self::streamFor((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(self::tryFopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
+ }
+
+ /**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ *
+ * @throws \RuntimeException if the file cannot be opened
+ */
+ public static function tryFopen(string $filename, string $mode)
+ {
+ $ex = null;
+ set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $errstr
+ ));
+
+ return true;
+ });
+
+ try {
+ /** @var resource $handle */
+ $handle = fopen($filename, $mode);
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var \RuntimeException $ex */
+ throw $ex;
+ }
+
+ return $handle;
+ }
+
+ /**
+ * Safely gets the contents of a given stream.
+ *
+ * When stream_get_contents fails, PHP normally raises a warning. This
+ * function adds an error handler that checks for errors and throws an
+ * exception instead.
+ *
+ * @param resource $stream
+ *
+ * @throws \RuntimeException if the stream cannot be read
+ */
+ public static function tryGetContents($stream): string
+ {
+ $ex = null;
+ set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to read stream contents: %s',
+ $errstr
+ ));
+
+ return true;
+ });
+
+ try {
+ /** @var string|false $contents */
+ $contents = stream_get_contents($stream);
+
+ if ($contents === false) {
+ $ex = new \RuntimeException('Unable to read stream contents');
+ }
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to read stream contents: %s',
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var \RuntimeException $ex */
+ throw $ex;
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or UriInterface and returns a
+ * UriInterface for the given value. If the value is already a
+ * UriInterface, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function uriFor($uri): UriInterface
+ {
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ }
+
+ if (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+ }
+}
diff --git a/vendor/monolog/monolog/CHANGELOG.md b/vendor/monolog/monolog/CHANGELOG.md
new file mode 100644
index 0000000..2c89f9d
--- /dev/null
+++ b/vendor/monolog/monolog/CHANGELOG.md
@@ -0,0 +1,810 @@
+### 3.10.0 (2026-01-02)
+
+ * Added automatic directory cleanup in RotatingFileHandler (#2000)
+ * Added timezone-aware file rotation to RotatingFileHandler (#1982)
+ * Added support for mongodb/mongodb 2.0+ (#1998)
+ * Added NoDiscard attribute to TestHandler methods to ensure the result is used (#2013)
+ * Fixed JsonFormatter crashing if __toString throws while normalizing data (#1968)
+ * Fixed PHP 8.5 deprecation warnings (#1997, #2009)
+ * Fixed DeduplicatingHandler collecting duplicate logs if the file cannot be locked (2e97231)
+ * Fixed GelfMessageFormatter to use integers instead of bool for gelf 1.1 support (#1973)
+ * Fixed empty stack traces being output anyway (#1979)
+ * Fixed StreamHandler not reopening the file if the inode changed (#1963)
+ * Fixed TelegramBotHandler sending empty messages (#1992)
+ * Fixed file paths in stack traces containing backslashes on windows, always using / now to unify logs (#1980)
+ * Fixed RotatingFileHandler unlink errors not being suppressed correctly (#1999)
+
+### 3.9.0 (2025-03-24)
+
+ * BC Warning: Fixed SendGridHandler to use the V3 API as V2 is now shut down, but this requires a new API key (#1952)
+ * Deprecated Monolog\Test\TestCase in favor of Monolog\Test\MonologTestCase (#1953)
+ * Added extension point for NativeMailerHandler::mail (#1948)
+ * Added setHandler method to BufferHandler to modify the nested handler at runtime (#1946)
+ * Fixed date format in ElasticsearchFormatter to use +00:00 vs +0000 tz identifiers (#1942)
+ * Fixed GelfMessageFormatter handling numeric context/extra keys (#1932)
+
+### 3.8.1 (2024-12-05)
+
+ * Deprecated Monolog\DateTimeImmutable in favor of Monolog\JsonSerializableDateTimeImmutable (#1928)
+ * Fixed gelf keys not being valid when context/extra data keys have spaces in them (#1927)
+ * Fixed empty lines appearing in the stack traces when a custom formatter returned null (#1925)
+
+### 3.8.0 (2024-11-12)
+
+ * Added `$fileOpenMode` param to `StreamHandler` to define a custom fopen mode to open the log file (#1913)
+ * Fixed PHP 8.4 deprecation notices (#1903)
+ * Added ability to extend/override `IntrospectionProcessor` (#1899)
+ * Added `$timeout` param to `ProcessHandler` to configure the stream_select() timeout to avoid blocking too long (default is 1.0 sec) (#1916)
+ * Fixed JsonFormatter batch handling to normalize records individually to make sure they look the same as if they were handled one by one (#1906)
+ * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882)
+ * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866)
+ * Fixed `StreamHandler::reset` not closing the stream, so that it would fail to write in some cases with long running processes (#1862)
+ * Fixed `RotatingFileHandler` issue where rotation does not happen in some long running processes (#1905)
+ * Fixed `JsonFormatter` handling of incomplete classes (#1834)
+ * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905)
+
+### 3.7.0 (2024-06-28)
+
+ * Added `NormalizerFormatter->setBasePath(...)` (and `JsonFormatter` by extension) that allows removing the project's path from the stack trace output (47e301d3e)
+ * Fixed JsonFormatter handling of incomplete classes (#1834)
+ * Fixed private error handlers causing problems with custom StreamHandler implementations (#1866)
+
+### 3.6.0 (2024-04-12)
+
+ * Added `LineFormatter->setBasePath(...)` that allows removing the project's path from the stack trace output (#1873)
+ * Added `$includeExtra` option in `PsrHandler` to also use extra data to replace placeholder values in the message (#1852)
+ * Added ability to customize what is a duplicated message by extending the `DeduplicationHandler` (#1879)
+ * Added handling for using `GelfMessageFormatter` together with the `AmqpHandler` (#1869)
+ * Added ability to extend `GoogleCloudLoggingFormatter` (#1859)
+ * Fixed `__toString` failures in context data crashing the normalization process (#1868)
+ * Fixed PHP 8.4 deprecation warnings (#1874)
+
+### 3.5.0 (2023-10-27)
+
+ * Added ability to indent stack traces in LineFormatter via e.g. `indentStacktraces(' ')` (#1835)
+ * Added ability to configure a max level name length in LineFormatter via e.g. `setMaxLevelNameLength(3)` (#1850)
+ * Added support for indexed arrays (i.e. `[]` and not `{}` arrays once json serialized) containing inline linebreaks in LineFormatter (#1818)
+ * Added `WithMonologChannel` attribute for integrators to use to configure autowiring (#1847)
+ * Fixed log record `extra` data leaking between handlers that have handler-specific processors set (#1819)
+ * Fixed LogglyHandler issue with record level filtering (#1841)
+ * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)
+ * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)
+ * Fixed normalization error when normalizing incomplete classes (#1833)
+
+### 3.4.0 (2023-06-21)
+
+ * Added `LoadAverageProcessor` to track one of the 1, 5 or 15min load averages (#1803)
+ * Added support for priority to the `AsMonologProcessor` attribute (#1797)
+ * Added `TelegramBotHandler` `topic`/`message_thread_id` support (#1802)
+ * Fixed `FingersCrossedHandler` passthruLevel checking (#1801)
+ * Fixed support of yearly and monthly rotation log file to rotate only once a month/year (#1805)
+ * Fixed `TestHandler` method docs (#1794)
+ * Fixed handling of falsey `display_errors` string values (#1804)
+
+### 3.3.1 (2023-02-06)
+
+ * Fixed Logger not being serializable anymore (#1792)
+
+### 3.3.0 (2023-02-06)
+
+ * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)
+ * Added `ClosureContextProcessor` to allow delaying the creation of context data by setting a Closure in context which is called when the log record is used (#1745)
+ * Added an ElasticsearchHandler option to set the `op_type` to `create` instead of the default `index` (#1766)
+ * Added support for enum context values in PsrLogMessageProcessor (#1773)
+ * Added graylog2/gelf-php 2.x support (#1747)
+ * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)
+ * Fixed GitProcessor not filtering correctly based on Level (#1749)
+ * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)
+ * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)
+ * Fixed infinite loop detection within Fibers (#1753)
+ * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)
+
+### 3.2.0 (2022-07-24)
+
+ * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)
+ * Marked `Logger` `@final` as it should not be extended, prefer composition or talk to us if you are missing something
+ * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)
+ * Added `SyslogFormatter` to output syslog-like files which can be consumed by tools like [lnav](https://lnav.org/) (#1689)
+ * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)
+ * Added `GoogleCloudLoggingFormatter` (#1719)
+ * Added support for Predis 2.x (#1732)
+ * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)
+ * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)
+ * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720)
+ * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)
+ * Fixed PHP 8.2 deprecation warnings (#1722)
+ * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)
+
+### 3.1.0 (2022-06-09)
+
+ * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)
+ * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)
+ * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)
+ * Fixed interop issue by removing the need for a return type in ProcessorInterface (#1680)
+ * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)
+ * Fixed RotatingFileHandler issue when the date format contained slashes (#1671)
+
+### 3.0.0 (2022-05-10)
+
+Changes from RC1
+
+- The `Monolog\LevelName` enum does not exist anymore, use `Monolog\Level->getName()` instead.
+
+### 3.0.0-RC1 (2022-05-08)
+
+This is mostly a cleanup release offering stronger type guarantees for integrators with the
+array->object/enum changes, but there is no big new feature for end users.
+
+See [UPGRADE notes](UPGRADE.md#300) for details on all breaking changes especially if you are extending/implementing Monolog classes/interfaces.
+
+Noteworthy BC Breaks:
+
+- The minimum supported PHP version is now `8.1.0`.
+- Log records have been converted from an array to a [`Monolog\LogRecord` object](src/Monolog/LogRecord.php)
+ with public (and mostly readonly) properties. e.g. instead of doing
+ `$record['context']` use `$record->context`.
+ In formatters or handlers if you rather need an array to work with you can use `$record->toArray()`
+ to get back a Monolog 1/2 style record array. This will contain the enum values instead of enum cases
+ in the `level` and `level_name` keys to be more backwards compatible and use simpler data types.
+- `FormatterInterface`, `HandlerInterface`, `ProcessorInterface`, etc. changed to contain `LogRecord $record`
+ instead of `array $record` parameter types. If you want to support multiple Monolog versions this should
+ be possible by type-hinting nothing, or `array|LogRecord` if you support PHP 8.0+. You can then code
+ against the $record using Monolog 2 style as LogRecord implements ArrayAccess for BC.
+ The interfaces do not require a `LogRecord` return type even where it would be applicable, but if you only
+ support Monolog 3 in integration code I would recommend you use `LogRecord` return types wherever fitting
+ to ensure forward compatibility as it may be added in Monolog 4.
+- Log levels are now enums [`Monolog\Level`](src/Monolog/Level.php) and [`Monolog\LevelName`](src/Monolog/LevelName.php)
+- Removed deprecated SwiftMailerHandler, migrate to SymfonyMailerHandler instead.
+- `ResettableInterface::reset()` now requires a void return type.
+- All properties have had types added, which may require you to do so as well if you extended
+ a Monolog class and declared the same property.
+
+New deprecations:
+
+- `Logger::DEBUG`, `Logger::ERROR`, etc. are now deprecated in favor of the `Monolog\Level` enum.
+ e.g. instead of `Logger::WARNING` use `Level::Warning` if you need to pass the enum case
+ to Monolog or one of its handlers, or `Level::Warning->value` if you need the integer
+ value equal to what `Logger::WARNING` was giving you.
+- `Logger::getLevelName()` is now deprecated.
+
+### 2.10.0 (2024-11-12)
+
+ * Added `$fileOpenMode` to `StreamHandler` to define a custom fopen mode to open the log file (#1913)
+ * Fixed `StreamHandler` handling of write failures so that it now closes/reopens the stream and retries the write once before failing (#1882)
+ * Fixed `StreamHandler` error handler causing issues if a stream handler triggers an error (#1866)
+ * Fixed `JsonFormatter` handling of incomplete classes (#1834)
+ * Fixed `RotatingFileHandler` bug where rotation could sometimes not happen correctly (#1905)
+
+### 2.9.3 (2024-04-12)
+
+ * Fixed PHP 8.4 deprecation warnings (#1874)
+
+### 2.9.2 (2023-10-27)
+
+ * Fixed display_errors parsing in ErrorHandler which did not support string values (#1804)
+ * Fixed bug where the previous error handler would not be restored in some cases where StreamHandler fails (#1815)
+ * Fixed normalization error when normalizing incomplete classes (#1833)
+
+### 2.9.1 (2023-02-06)
+
+ * Fixed Logger not being serializable anymore (#1792)
+
+### 2.9.0 (2023-02-05)
+
+ * Deprecated FlowdockHandler & Formatter as the flowdock service was shutdown (#1748)
+ * Added support for enum context values in PsrLogMessageProcessor (#1773)
+ * Added graylog2/gelf-php 2.x support (#1747)
+ * Improved `BrowserConsoleHandler` logging to use more appropriate methods than just console.log in the browser (#1739)
+ * Fixed `WhatFailureGroupHandler` not catching errors happening inside `close()` (#1791)
+ * Fixed datetime field in `GoogleCloudLoggingFormatter` (#1758)
+ * Fixed infinite loop detection within Fibers (#1753)
+ * Fixed `AmqpHandler->setExtraAttributes` not working with buffering handler wrappers (#1781)
+
+### 2.8.0 (2022-07-24)
+
+ * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)
+ * Added RFC 5424 level (`7` to `0`) support to `Logger::log` and `Logger::addRecord` to increase interoperability (#1723)
+ * Added support for `__toString` for objects which are not json serializable in `JsonFormatter` (#1733)
+ * Added `GoogleCloudLoggingFormatter` (#1719)
+ * Added support for Predis 2.x (#1732)
+ * Added `AmqpHandler->setExtraAttributes` to allow configuring attributes when using an AMQPExchange (#1724)
+ * Fixed serialization/unserialization of handlers to make sure private properties are included (#1727)
+ * Fixed allowInlineLineBreaks in LineFormatter causing issues with windows paths containing `\n` or `\r` sequences (#1720)
+ * Fixed max normalization depth not being taken into account when formatting exceptions with a deep chain of previous exceptions (#1726)
+ * Fixed PHP 8.2 deprecation warnings (#1722)
+ * Fixed rare race condition or filesystem issue where StreamHandler is unable to create the directory the log should go into yet it exists already (#1678)
+
+### 2.7.0 (2022-06-09)
+
+ * Added `$datetime` parameter to `Logger::addRecord` as low level API to allow logging into the past or future (#1682)
+ * Added `Logger::useLoggingLoopDetection` to allow disabling cyclic logging detection in concurrent frameworks (#1681)
+ * Fixed handling of fatal errors if callPrevious is disabled in ErrorHandler (#1670)
+ * Marked the reusable `Monolog\Test\TestCase` class as `@internal` to make sure PHPStorm does not show it above PHPUnit, you may still use it to test your own handlers/etc though (#1677)
+ * Fixed RotatingFileHandler issue when the date format contained slashes (#1671)
+
+### 2.6.0 (2022-05-10)
+
+ * Deprecated `SwiftMailerHandler`, use `SymfonyMailerHandler` instead
+ * Added `SymfonyMailerHandler` (#1663)
+ * Added ElasticSearch 8.x support to the ElasticsearchHandler (#1662)
+ * Added a way to filter/modify stack traces in LineFormatter (#1665)
+ * Fixed UdpSocket not being able to reopen/reconnect after close()
+ * Fixed infinite loops if a Handler is triggering logging while handling log records
+
+### 2.5.0 (2022-04-08)
+
+ * Added `callType` to IntrospectionProcessor (#1612)
+ * Fixed AsMonologProcessor syntax to be compatible with PHP 7.2 (#1651)
+
+### 2.4.0 (2022-03-14)
+
+ * Added [`Monolog\LogRecord`](src/Monolog/LogRecord.php) interface that can be used to type-hint records like `array|\Monolog\LogRecord $record` to be forward compatible with the upcoming Monolog 3 changes
+ * Added `includeStacktraces` constructor params to LineFormatter & JsonFormatter (#1603)
+ * Added `persistent`, `timeout`, `writingTimeout`, `connectionTimeout`, `chunkSize` constructor params to SocketHandler and derivatives (#1600)
+ * Added `AsMonologProcessor` PHP attribute which can help autowiring / autoconfiguration of processors if frameworks / integrations decide to make use of it. This is useless when used purely with Monolog (#1637)
+ * Added support for keeping native BSON types as is in MongoDBFormatter (#1620)
+ * Added support for a `user_agent` key in WebProcessor, disabled by default but you can use it by configuring the $extraFields you want (#1613)
+ * Added support for username/userIcon in SlackWebhookHandler (#1617)
+ * Added extension points to BrowserConsoleHandler (#1593)
+ * Added record message/context/extra info to exceptions thrown when a StreamHandler cannot open its stream to avoid completely losing the data logged (#1630)
+ * Fixed error handler signature to accept a null $context which happens with internal PHP errors (#1614)
+ * Fixed a few setter methods not returning `self` (#1609)
+ * Fixed handling of records going over the max Telegram message length (#1616)
+
+### 2.3.5 (2021-10-01)
+
+ * Fixed regression in StreamHandler since 2.3.3 on systems with the memory_limit set to >=20GB (#1592)
+
+### 2.3.4 (2021-09-15)
+
+ * Fixed support for psr/log 3.x (#1589)
+
+### 2.3.3 (2021-09-14)
+
+ * Fixed memory usage when using StreamHandler and calling stream_get_contents on the resource you passed to it (#1578, #1577)
+ * Fixed support for psr/log 2.x (#1587)
+ * Fixed some type annotations
+
+### 2.3.2 (2021-07-23)
+
+ * Fixed compatibility with PHP 7.2 - 7.4 when experiencing PCRE errors (#1568)
+
+### 2.3.1 (2021-07-14)
+
+ * Fixed Utils::getClass handling of anonymous classes not being fully compatible with PHP 8 (#1563)
+ * Fixed some `@inheritDoc` annotations having the wrong case
+
+### 2.3.0 (2021-07-05)
+
+ * Added a ton of PHPStan type annotations as well as type aliases on Monolog\Logger for Record, Level and LevelName that you can import (#1557)
+ * Added ability to customize date format when using JsonFormatter (#1561)
+ * Fixed FilterHandler not calling reset on its internal handler when reset() is called on it (#1531)
+ * Fixed SyslogUdpHandler not setting the timezone correctly on DateTimeImmutable instances (#1540)
+ * Fixed StreamHandler thread safety - chunk size set to 2GB now to avoid interlacing when doing concurrent writes (#1553)
+
+### 2.2.0 (2020-12-14)
+
+ * Added JSON_PARTIAL_OUTPUT_ON_ERROR to default json encoding flags, to avoid dropping entire context data or even records due to an invalid subset of it somewhere
+ * Added setDateFormat to NormalizerFormatter (and Line/Json formatters by extension) to allow changing this after object creation
+ * Added RedisPubSubHandler to log records to a Redis channel using PUBLISH
+ * Added support for Elastica 7, and deprecated the $type argument of ElasticaFormatter which is not in use anymore as of Elastica 7
+ * Added support for millisecond write timeouts in SocketHandler, you can now pass floats to setWritingTimeout, e.g. 0.2 is 200ms
+ * Added support for unix sockets in SyslogUdpHandler (set $port to 0 to make the $host a unix socket)
+ * Added handleBatch support for TelegramBotHandler
+ * Added RFC5424e extended date format including milliseconds to SyslogUdpHandler
+ * Added support for configuring handlers with numeric level values in strings (coming from e.g. env vars)
+ * Fixed Wildfire/FirePHP/ChromePHP handling of unicode characters
+ * Fixed PHP 8 issues in SyslogUdpHandler
+ * Fixed internal type error when mbstring is missing
+
+### 2.1.1 (2020-07-23)
+
+ * Fixed removing of json encoding options
+ * Fixed type hint of $level not accepting strings in SendGridHandler and OverflowHandler
+ * Fixed SwiftMailerHandler not accepting email templates with an empty subject
+ * Fixed array access on null in RavenHandler
+ * Fixed unique_id in WebProcessor not being disableable
+
+### 2.1.0 (2020-05-22)
+
+ * Added `JSON_INVALID_UTF8_SUBSTITUTE` to default json flags, so that invalid UTF8 characters now get converted to [�](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character) instead of being converted from ISO-8859-15 to UTF8 as it was before, which was hardly a comprehensive solution
+ * Added `$ignoreEmptyContextAndExtra` option to JsonFormatter to skip empty context/extra entirely from the output
+ * Added `$parseMode`, `$disableWebPagePreview` and `$disableNotification` options to TelegramBotHandler
+ * Added tentative support for PHP 8
+ * NormalizerFormatter::addJsonEncodeOption and removeJsonEncodeOption are now public to allow modifying default json flags
+ * Fixed GitProcessor type error when there is no git repo present
+ * Fixed normalization of SoapFault objects containing deeply nested objects as "detail"
+ * Fixed support for relative paths in RotatingFileHandler
+
+### 2.0.2 (2019-12-20)
+
+ * Fixed ElasticsearchHandler swallowing exceptions details when failing to index log records
+ * Fixed normalization of SoapFault objects containing non-strings as "detail" in LineFormatter
+ * Fixed formatting of resources in JsonFormatter
+ * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)
+ * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it
+ * Fixed Turkish locale messing up the conversion of level names to their constant values
+
+### 2.0.1 (2019-11-13)
+
+ * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable
+ * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler, OverflowHandler and SamplingHandler
+ * Fixed BrowserConsoleHandler formatting when using multiple styles
+ * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings
+ * Fixed normalization of SoapFault objects containing non-strings as "detail"
+ * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding
+ * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).
+ * Fixed type error in BrowserConsoleHandler when the context array of log records was not associative.
+
+### 2.0.0 (2019-08-30)
+
+ * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
+ * BC Break: Logger methods log/debug/info/notice/warning/error/critical/alert/emergency now have explicit void return types
+ * Added FallbackGroupHandler which works like the WhatFailureGroupHandler but stops dispatching log records as soon as one handler accepted it
+ * Fixed support for UTF-8 when cutting strings to avoid cutting a multibyte-character in half
+ * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases
+ * Fixed date timezone handling in SyslogUdpHandler
+
+### 2.0.0-beta2 (2019-07-06)
+
+ * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
+ * BC Break: PHP 7.2 is now the minimum required PHP version.
+ * BC Break: Removed SlackbotHandler, RavenHandler and HipChatHandler, see [UPGRADE.md](UPGRADE.md) for details
+ * Added OverflowHandler which will only flush log records to its nested handler when reaching a certain amount of logs (i.e. only pass through when things go really bad)
+ * Added TelegramBotHandler to log records to a [Telegram](https://core.telegram.org/bots/api) bot account
+ * Added support for JsonSerializable when normalizing exceptions
+ * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler
+ * Added SoapFault details to formatted exceptions
+ * Fixed DeduplicationHandler silently failing to start when file could not be opened
+ * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records
+ * Fixed GelfFormatter losing some data when one attachment was too long
+ * Fixed issue in SignalHandler restarting syscalls functionality
+ * Improved performance of LogglyHandler when sending multiple logs in a single request
+
+### 2.0.0-beta1 (2018-12-08)
+
+ * BC Break: This is a major release, see [UPGRADE.md](UPGRADE.md) for details if you are coming from a 1.x release
+ * BC Break: PHP 7.1 is now the minimum required PHP version.
+ * BC Break: Quite a few interface changes, only relevant if you implemented your own handlers/processors/formatters
+ * BC Break: Removed non-PSR-3 methods to add records, all the `add*` (e.g. `addWarning`) methods as well as `emerg`, `crit`, `err` and `warn`
+ * BC Break: The record timezone is now set per Logger instance and not statically anymore
+ * BC Break: There is no more default handler configured on empty Logger instances
+ * BC Break: ElasticSearchHandler renamed to ElasticaHandler
+ * BC Break: Various handler-specific breaks, see [UPGRADE.md](UPGRADE.md) for details
+ * Added scalar type hints and return hints in all the places it was possible. Switched strict_types on for more reliability.
+ * Added DateTimeImmutable support, all record datetime are now immutable, and will toString/json serialize with the correct date format, including microseconds (unless disabled)
+ * Added timezone and microseconds to the default date format
+ * Added SendGridHandler to use the SendGrid API to send emails
+ * Added LogmaticHandler to use the Logmatic.io API to store log records
+ * Added SqsHandler to send log records to an AWS SQS queue
+ * Added ElasticsearchHandler to send records via the official ES library. Elastica users should now use ElasticaHandler instead of ElasticSearchHandler
+ * Added NoopHandler which is similar to the NullHandle but does not prevent the bubbling of log records to handlers further down the configuration, useful for temporarily disabling a handler in configuration files
+ * Added ProcessHandler to write log output to the STDIN of a given process
+ * Added HostnameProcessor that adds the machine's hostname to log records
+ * Added a `$dateFormat` option to the PsrLogMessageProcessor which lets you format DateTime instances nicely
+ * Added support for the PHP 7.x `mongodb` extension in the MongoDBHandler
+ * Fixed many minor issues in various handlers, and probably added a few regressions too
+
+### 1.26.1 (2021-05-28)
+
+ * Fixed PHP 8.1 deprecation warning
+
+### 1.26.0 (2020-12-14)
+
+ * Added $dateFormat and $removeUsedContextFields arguments to PsrLogMessageProcessor (backport from 2.x)
+
+### 1.25.5 (2020-07-23)
+
+ * Fixed array access on null in RavenHandler
+ * Fixed unique_id in WebProcessor not being disableable
+
+### 1.25.4 (2020-05-22)
+
+ * Fixed GitProcessor type error when there is no git repo present
+ * Fixed normalization of SoapFault objects containing deeply nested objects as "detail"
+ * Fixed support for relative paths in RotatingFileHandler
+
+### 1.25.3 (2019-12-20)
+
+ * Fixed formatting of resources in JsonFormatter
+ * Fixed RedisHandler failing to use MULTI properly when passed a proxied Redis instance (e.g. in Symfony with lazy services)
+ * Fixed FilterHandler triggering a notice when handleBatch was filtering all records passed to it
+ * Fixed Turkish locale messing up the conversion of level names to their constant values
+
+### 1.25.2 (2019-11-13)
+
+ * Fixed normalization of Traversables to avoid traversing them as not all of them are rewindable
+ * Fixed setFormatter/getFormatter to forward to the nested handler in FilterHandler, FingersCrossedHandler, BufferHandler and SamplingHandler
+ * Fixed BrowserConsoleHandler formatting when using multiple styles
+ * Fixed normalization of exception codes to be always integers even for PDOException which have them as numeric strings
+ * Fixed normalization of SoapFault objects containing non-strings as "detail"
+ * Fixed json encoding across all handlers to always attempt recovery of non-UTF-8 strings instead of failing the whole encoding
+
+### 1.25.1 (2019-09-06)
+
+ * Fixed forward-compatible interfaces to be compatible with Monolog 1.x too.
+
+### 1.25.0 (2019-09-06)
+
+ * Deprecated SlackbotHandler, use SlackWebhookHandler or SlackHandler instead
+ * Deprecated RavenHandler, use sentry/sentry 2.x and their Sentry\Monolog\Handler instead
+ * Deprecated HipChatHandler, migrate to Slack and use SlackWebhookHandler or SlackHandler instead
+ * Added forward-compatible interfaces and traits FormattableHandlerInterface, FormattableHandlerTrait, ProcessableHandlerInterface, ProcessableHandlerTrait. If you use modern PHP and want to make code compatible with Monolog 1 and 2 this can help. You will have to require at least Monolog 1.25 though.
+ * Added support for RFC3164 (outdated BSD syslog protocol) to SyslogUdpHandler
+ * Fixed issue in GroupHandler and WhatFailureGroupHandler where setting multiple processors would duplicate records
+ * Fixed issue in SignalHandler restarting syscalls functionality
+ * Fixed normalizers handling of exception backtraces to avoid serializing arguments in some cases
+ * Fixed ZendMonitorHandler to work with the latest Zend Server versions
+ * Fixed ChromePHPHandler to avoid sending more data than latest Chrome versions allow in headers (4KB down from 256KB).
+
+### 1.24.0 (2018-11-05)
+
+ * BC Notice: If you are extending any of the Monolog's Formatters' `normalize` method, make sure you add the new `$depth = 0` argument to your function signature to avoid strict PHP warnings.
+ * Added a `ResettableInterface` in order to reset/reset/clear/flush handlers and processors
+ * Added a `ProcessorInterface` as an optional way to label a class as being a processor (mostly useful for autowiring dependency containers)
+ * Added a way to log signals being received using Monolog\SignalHandler
+ * Added ability to customize error handling at the Logger level using Logger::setExceptionHandler
+ * Added InsightOpsHandler to migrate users of the LogEntriesHandler
+ * Added protection to NormalizerFormatter against circular and very deep structures, it now stops normalizing at a depth of 9
+ * Added capture of stack traces to ErrorHandler when logging PHP errors
+ * Added RavenHandler support for a `contexts` context or extra key to forward that to Sentry's contexts
+ * Added forwarding of context info to FluentdFormatter
+ * Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example
+ * Added ability to extend/override BrowserConsoleHandler
+ * Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility
+ * Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility
+ * Dropped official support for HHVM in test builds
+ * Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain
+ * Fixed naming of fields in Slack handler, all field names are now capitalized in all cases
+ * Fixed HipChatHandler bug where slack dropped messages randomly
+ * Fixed normalization of objects in Slack handlers
+ * Fixed support for PHP7's Throwable in NewRelicHandler
+ * Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory
+ * Fixed table row styling issues in HtmlFormatter
+ * Fixed RavenHandler dropping the message when logging exception
+ * Fixed WhatFailureGroupHandler skipping processors when using handleBatch
+ and implement it where possible
+ * Fixed display of anonymous class names
+
+### 1.23.0 (2017-06-19)
+
+ * Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
+ * Fixed GelfHandler truncation to be per field and not per message
+ * Fixed compatibility issue with PHP <5.3.6
+ * Fixed support for headless Chrome in ChromePHPHandler
+ * Fixed support for latest Aws SDK in DynamoDbHandler
+ * Fixed support for SwiftMailer 6.0+ in SwiftMailerHandler
+
+### 1.22.1 (2017-03-13)
+
+ * Fixed lots of minor issues in the new Slack integrations
+ * Fixed support for allowInlineLineBreaks in LineFormatter when formatting exception backtraces
+
+### 1.22.0 (2016-11-26)
+
+ * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily
+ * Added MercurialProcessor to add mercurial revision and branch names to log records
+ * Added support for AWS SDK v3 in DynamoDbHandler
+ * Fixed fatal errors occurring when normalizing generators that have been fully consumed
+ * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix)
+ * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore
+ * Fixed SyslogUdpHandler to avoid sending empty frames
+ * Fixed a few PHP 7.0 and 7.1 compatibility issues
+
+### 1.21.0 (2016-07-29)
+
+ * Break: Reverted the addition of $context when the ErrorHandler handles regular php errors from 1.20.0 as it was causing issues
+ * Added support for more formats in RotatingFileHandler::setFilenameFormat as long as they have Y, m and d in order
+ * Added ability to format the main line of text the SlackHandler sends by explicitly setting a formatter on the handler
+ * Added information about SoapFault instances in NormalizerFormatter
+ * Added $handleOnlyReportedErrors option on ErrorHandler::registerErrorHandler (default true) to allow logging of all errors no matter the error_reporting level
+
+### 1.20.0 (2016-07-02)
+
+ * Added FingersCrossedHandler::activate() to manually trigger the handler regardless of the activation policy
+ * Added StreamHandler::getUrl to retrieve the stream's URL
+ * Added ability to override addRow/addTitle in HtmlFormatter
+ * Added the $context to context information when the ErrorHandler handles a regular php error
+ * Deprecated RotatingFileHandler::setFilenameFormat to only support 3 formats: Y, Y-m and Y-m-d
+ * Fixed WhatFailureGroupHandler to work with PHP7 throwables
+ * Fixed a few minor bugs
+
+### 1.19.0 (2016-04-12)
+
+ * Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
+ * Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
+ * Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
+ * Fixed HipChatHandler handling of long messages
+
+### 1.18.2 (2016-04-02)
+
+ * Fixed ElasticaFormatter to use more precise dates
+ * Fixed GelfMessageFormatter sending too long messages
+
+### 1.18.1 (2016-03-13)
+
+ * Fixed SlackHandler bug where slack dropped messages randomly
+ * Fixed RedisHandler issue when using with the PHPRedis extension
+ * Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
+ * Fixed BrowserConsoleHandler regression
+
+### 1.18.0 (2016-03-01)
+
+ * Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
+ * Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
+ * Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
+ * Added FluentdFormatter for the Fluentd unix socket protocol
+ * Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
+ * Added support for replacing context sub-keys using `%context.*%` in LineFormatter
+ * Added support for `payload` context value in RollbarHandler
+ * Added setRelease to RavenHandler to describe the application version, sent with every log
+ * Added support for `fingerprint` context value in RavenHandler
+ * Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
+ * Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
+ * Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places
+
+### 1.17.2 (2015-10-14)
+
+ * Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers
+ * Fixed SlackHandler handling to use slack functionalities better
+ * Fixed SwiftMailerHandler bug when sending multiple emails they all had the same id
+ * Fixed 5.3 compatibility regression
+
+### 1.17.1 (2015-08-31)
+
+ * Fixed RollbarHandler triggering PHP notices
+
+### 1.17.0 (2015-08-30)
+
+ * Added support for `checksum` and `release` context/extra values in RavenHandler
+ * Added better support for exceptions in RollbarHandler
+ * Added UidProcessor::getUid
+ * Added support for showing the resource type in NormalizedFormatter
+ * Fixed IntrospectionProcessor triggering PHP notices
+
+### 1.16.0 (2015-08-09)
+
+ * Added IFTTTHandler to notify ifttt.com triggers
+ * Added Logger::setHandlers() to allow setting/replacing all handlers
+ * Added $capSize in RedisHandler to cap the log size
+ * Fixed StreamHandler creation of directory to only trigger when the first log write happens
+ * Fixed bug in the handling of curl failures
+ * Fixed duplicate logging of fatal errors when both error and fatal error handlers are registered in monolog's ErrorHandler
+ * Fixed missing fatal errors records with handlers that need to be closed to flush log records
+ * Fixed TagProcessor::addTags support for associative arrays
+
+### 1.15.0 (2015-07-12)
+
+ * Added addTags and setTags methods to change a TagProcessor
+ * Added automatic creation of directories if they are missing for a StreamHandler to open a log file
+ * Added retry functionality to Loggly, Cube and Mandrill handlers so they retry up to 5 times in case of network failure
+ * Fixed process exit code being incorrectly reset to 0 if ErrorHandler::registerExceptionHandler was used
+ * Fixed HTML/JS escaping in BrowserConsoleHandler
+ * Fixed JSON encoding errors being silently suppressed (PHP 5.5+ only)
+
+### 1.14.0 (2015-06-19)
+
+ * Added PHPConsoleHandler to send record to Chrome's PHP Console extension and library
+ * Added support for objects implementing __toString in the NormalizerFormatter
+ * Added support for HipChat's v2 API in HipChatHandler
+ * Added Logger::setTimezone() to initialize the timezone monolog should use in case date.timezone isn't correct for your app
+ * Added an option to send formatted message instead of the raw record on PushoverHandler via ->useFormattedMessage(true)
+ * Fixed curl errors being silently suppressed
+
+### 1.13.1 (2015-03-09)
+
+ * Fixed regression in HipChat requiring a new token to be created
+
+### 1.13.0 (2015-03-05)
+
+ * Added Registry::hasLogger to check for the presence of a logger instance
+ * Added context.user support to RavenHandler
+ * Added HipChat API v2 support in the HipChatHandler
+ * Added NativeMailerHandler::addParameter to pass params to the mail() process
+ * Added context data to SlackHandler when $includeContextAndExtra is true
+ * Added ability to customize the Swift_Message per-email in SwiftMailerHandler
+ * Fixed SwiftMailerHandler to lazily create message instances if a callback is provided
+ * Fixed serialization of INF and NaN values in Normalizer and LineFormatter
+
+### 1.12.0 (2014-12-29)
+
+ * Break: HandlerInterface::isHandling now receives a partial record containing only a level key. This was always the intent and does not break any Monolog handler but is strictly speaking a BC break and you should check if you relied on any other field in your own handlers.
+ * Added PsrHandler to forward records to another PSR-3 logger
+ * Added SamplingHandler to wrap around a handler and include only every Nth record
+ * Added MongoDBFormatter to support better storage with MongoDBHandler (it must be enabled manually for now)
+ * Added exception codes in the output of most formatters
+ * Added LineFormatter::includeStacktraces to enable exception stack traces in logs (uses more than one line)
+ * Added $useShortAttachment to SlackHandler to minify attachment size and $includeExtra to append extra data
+ * Added $host to HipChatHandler for users of private instances
+ * Added $transactionName to NewRelicHandler and support for a transaction_name context value
+ * Fixed MandrillHandler to avoid outputting API call responses
+ * Fixed some non-standard behaviors in SyslogUdpHandler
+
+### 1.11.0 (2014-09-30)
+
+ * Break: The NewRelicHandler extra and context data are now prefixed with extra_ and context_ to avoid clashes. Watch out if you have scripts reading those from the API and rely on names
+ * Added WhatFailureGroupHandler to suppress any exception coming from the wrapped handlers and avoid chain failures if a logging service fails
+ * Added MandrillHandler to send emails via the Mandrillapp.com API
+ * Added SlackHandler to log records to a Slack.com account
+ * Added FleepHookHandler to log records to a Fleep.io account
+ * Added LogglyHandler::addTag to allow adding tags to an existing handler
+ * Added $ignoreEmptyContextAndExtra to LineFormatter to avoid empty [] at the end
+ * Added $useLocking to StreamHandler and RotatingFileHandler to enable flock() while writing
+ * Added support for PhpAmqpLib in the AmqpHandler
+ * Added FingersCrossedHandler::clear and BufferHandler::clear to reset them between batches in long running jobs
+ * Added support for adding extra fields from $_SERVER in the WebProcessor
+ * Fixed support for non-string values in PrsLogMessageProcessor
+ * Fixed SwiftMailer messages being sent with the wrong date in long running scripts
+ * Fixed minor PHP 5.6 compatibility issues
+ * Fixed BufferHandler::close being called twice
+
+### 1.10.0 (2014-06-04)
+
+ * Added Logger::getHandlers() and Logger::getProcessors() methods
+ * Added $passthruLevel argument to FingersCrossedHandler to let it always pass some records through even if the trigger level is not reached
+ * Added support for extra data in NewRelicHandler
+ * Added $expandNewlines flag to the ErrorLogHandler to create multiple log entries when a message has multiple lines
+
+### 1.9.1 (2014-04-24)
+
+ * Fixed regression in RotatingFileHandler file permissions
+ * Fixed initialization of the BufferHandler to make sure it gets flushed after receiving records
+ * Fixed ChromePHPHandler and FirePHPHandler's activation strategies to be more conservative
+
+### 1.9.0 (2014-04-20)
+
+ * Added LogEntriesHandler to send logs to a LogEntries account
+ * Added $filePermissions to tweak file mode on StreamHandler and RotatingFileHandler
+ * Added $useFormatting flag to MemoryProcessor to make it send raw data in bytes
+ * Added support for table formatting in FirePHPHandler via the table context key
+ * Added a TagProcessor to add tags to records, and support for tags in RavenHandler
+ * Added $appendNewline flag to the JsonFormatter to enable using it when logging to files
+ * Added sound support to the PushoverHandler
+ * Fixed multi-threading support in StreamHandler
+ * Fixed empty headers issue when ChromePHPHandler received no records
+ * Fixed default format of the ErrorLogHandler
+
+### 1.8.0 (2014-03-23)
+
+ * Break: the LineFormatter now strips newlines by default because this was a bug, set $allowInlineLineBreaks to true if you need them
+ * Added BrowserConsoleHandler to send logs to any browser's console via console.log() injection in the output
+ * Added FilterHandler to filter records and only allow those of a given list of levels through to the wrapped handler
+ * Added FlowdockHandler to send logs to a Flowdock account
+ * Added RollbarHandler to send logs to a Rollbar account
+ * Added HtmlFormatter to send prettier log emails with colors for each log level
+ * Added GitProcessor to add the current branch/commit to extra record data
+ * Added a Monolog\Registry class to allow easier global access to pre-configured loggers
+ * Added support for the new official graylog2/gelf-php lib for GelfHandler, upgrade if you can by replacing the mlehner/gelf-php requirement
+ * Added support for HHVM
+ * Added support for Loggly batch uploads
+ * Added support for tweaking the content type and encoding in NativeMailerHandler
+ * Added $skipClassesPartials to tweak the ignored classes in the IntrospectionProcessor
+ * Fixed batch request support in GelfHandler
+
+### 1.7.0 (2013-11-14)
+
+ * Added ElasticSearchHandler to send logs to an Elastic Search server
+ * Added DynamoDbHandler and ScalarFormatter to send logs to Amazon's Dynamo DB
+ * Added SyslogUdpHandler to send logs to a remote syslogd server
+ * Added LogglyHandler to send logs to a Loggly account
+ * Added $level to IntrospectionProcessor so it only adds backtraces when needed
+ * Added $version to LogstashFormatter to allow using the new v1 Logstash format
+ * Added $appName to NewRelicHandler
+ * Added configuration of Pushover notification retries/expiry
+ * Added $maxColumnWidth to NativeMailerHandler to change the 70 chars default
+ * Added chainability to most setters for all handlers
+ * Fixed RavenHandler batch processing so it takes the message from the record with highest priority
+ * Fixed HipChatHandler batch processing so it sends all messages at once
+ * Fixed issues with eAccelerator
+ * Fixed and improved many small things
+
+### 1.6.0 (2013-07-29)
+
+ * Added HipChatHandler to send logs to a HipChat chat room
+ * Added ErrorLogHandler to send logs to PHP's error_log function
+ * Added NewRelicHandler to send logs to NewRelic's service
+ * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
+ * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
+ * Added stack traces output when normalizing exceptions (json output & co)
+ * Added Monolog\Logger::API constant (currently 1)
+ * Added support for ChromePHP's v4.0 extension
+ * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
+ * Added support for sending messages to multiple users at once with the PushoverHandler
+ * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
+ * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
+ * Fixed issue in RotatingFileHandler when an open_basedir restriction is active
+ * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
+ * Fixed SyslogHandler issue when many were used concurrently with different facilities
+
+### 1.5.0 (2013-04-23)
+
+ * Added ProcessIdProcessor to inject the PID in log records
+ * Added UidProcessor to inject a unique identifier to all log records of one request/run
+ * Added support for previous exceptions in the LineFormatter exception serialization
+ * Added Monolog\Logger::getLevels() to get all available levels
+ * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle
+
+### 1.4.1 (2013-04-01)
+
+ * Fixed exception formatting in the LineFormatter to be more minimalistic
+ * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
+ * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
+ * Fixed WebProcessor array access so it checks for data presence
+ * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors
+
+### 1.4.0 (2013-02-13)
+
+ * Added RedisHandler to log to Redis via the Predis library or the phpredis extension
+ * Added ZendMonitorHandler to log to the Zend Server monitor
+ * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
+ * Added `$useSSL` option to the PushoverHandler which is enabled by default
+ * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
+ * Fixed header injection capability in the NativeMailHandler
+
+### 1.3.1 (2013-01-11)
+
+ * Fixed LogstashFormatter to be usable with stream handlers
+ * Fixed GelfMessageFormatter levels on Windows
+
+### 1.3.0 (2013-01-08)
+
+ * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
+ * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
+ * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
+ * Added PushoverHandler to send mobile notifications
+ * Added CouchDBHandler and DoctrineCouchDBHandler
+ * Added RavenHandler to send data to Sentry servers
+ * Added support for the new MongoClient class in MongoDBHandler
+ * Added microsecond precision to log records' timestamps
+ * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
+ the oldest entries
+ * Fixed normalization of objects with cyclic references
+
+### 1.2.1 (2012-08-29)
+
+ * Added new $logopts arg to SyslogHandler to provide custom openlog options
+ * Fixed fatal error in SyslogHandler
+
+### 1.2.0 (2012-08-18)
+
+ * Added AmqpHandler (for use with AMQP servers)
+ * Added CubeHandler
+ * Added NativeMailerHandler::addHeader() to send custom headers in mails
+ * Added the possibility to specify more than one recipient in NativeMailerHandler
+ * Added the possibility to specify float timeouts in SocketHandler
+ * Added NOTICE and EMERGENCY levels to conform with RFC 5424
+ * Fixed the log records to use the php default timezone instead of UTC
+ * Fixed BufferHandler not being flushed properly on PHP fatal errors
+ * Fixed normalization of exotic resource types
+ * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
+
+### 1.1.0 (2012-04-23)
+
+ * Added Monolog\Logger::isHandling() to check if a handler will
+ handle the given log level
+ * Added ChromePHPHandler
+ * Added MongoDBHandler
+ * Added GelfHandler (for use with Graylog2 servers)
+ * Added SocketHandler (for use with syslog-ng for example)
+ * Added NormalizerFormatter
+ * Added the possibility to change the activation strategy of the FingersCrossedHandler
+ * Added possibility to show microseconds in logs
+ * Added `server` and `referer` to WebProcessor output
+
+### 1.0.2 (2011-10-24)
+
+ * Fixed bug in IE with large response headers and FirePHPHandler
+
+### 1.0.1 (2011-08-25)
+
+ * Added MemoryPeakUsageProcessor and MemoryUsageProcessor
+ * Added Monolog\Logger::getName() to get a logger's channel name
+
+### 1.0.0 (2011-07-06)
+
+ * Added IntrospectionProcessor to get info from where the logger was called
+ * Fixed WebProcessor in CLI
+
+### 1.0.0-RC1 (2011-07-01)
+
+ * Initial release
diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE
new file mode 100644
index 0000000..aa2a042
--- /dev/null
+++ b/vendor/monolog/monolog/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011-2020 Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/monolog/monolog/README.md b/vendor/monolog/monolog/README.md
new file mode 100644
index 0000000..0564f96
--- /dev/null
+++ b/vendor/monolog/monolog/README.md
@@ -0,0 +1,136 @@
+
+
+# Monolog - Logging for PHP [](https://github.com/Seldaek/monolog/actions)
+
+[](https://packagist.org/packages/monolog/monolog)
+[](https://packagist.org/packages/monolog/monolog)
+
+>**Note** This is the **documentation for Monolog 3.x**, if you are using older releases
+>see the documentation for [Monolog 2.x](https://github.com/Seldaek/monolog/blob/2.x/README.md) or [Monolog 1.x](https://github.com/Seldaek/monolog/blob/1.x/README.md)
+
+Monolog sends your logs to files, sockets, inboxes, databases and various
+web services. See the complete list of handlers below. Special handlers
+allow you to build advanced logging strategies.
+
+This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+interface that you can type-hint against in your own libraries to keep
+a maximum of interoperability. You can also use it in your applications to
+make sure you can always use another compatible logger at a later time.
+As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels.
+Internally Monolog still uses its own level scheme since it predates PSR-3.
+
+
+
+## Installation
+
+Install the latest version with
+
+```bash
+composer require monolog/monolog
+```
+
+## Basic Usage
+
+```php
+pushHandler(new StreamHandler('path/to/your.log', Level::Warning));
+
+// add records to the log
+$log->warning('Foo');
+$log->error('Bar');
+```
+
+## Documentation
+
+- [Usage Instructions](doc/01-usage.md)
+- [Handlers, Formatters and Processors](doc/02-handlers-formatters-processors.md)
+- [Utility Classes](doc/03-utilities.md)
+- [Extending Monolog](doc/04-extending.md)
+- [Log Record Structure](doc/message-structure.md)
+
+## Support Monolog Financially
+
+Get supported Monolog and help fund the project with the [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-monolog-monolog?utm_source=packagist-monolog-monolog&utm_medium=referral&utm_campaign=enterprise) or via [GitHub sponsorship](https://github.com/sponsors/Seldaek).
+
+Tidelift delivers commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
+
+## Third Party Packages
+
+Third party handlers, formatters and processors are
+[listed in the wiki](https://github.com/Seldaek/monolog/wiki/Third-Party-Packages). You
+can also add your own there if you publish one.
+
+## About
+
+### Requirements
+
+- Monolog `^3.0` works with PHP 8.1 or above.
+- Monolog `^2.5` works with PHP 7.2 or above.
+- Monolog `^1.25` works with PHP 5.3 up to 8.1, but is not very maintained anymore and will not receive PHP support fixes anymore.
+
+### Support
+
+Monolog 1.x support is somewhat limited at this point and only important fixes will be done. You should migrate to Monolog 2 or 3 where possible to benefit from all the latest features and fixes.
+
+### Submitting bugs and feature requests
+
+Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)
+
+### Framework Integrations
+
+- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+ can be used very easily with Monolog since it implements the interface.
+- [Symfony](http://symfony.com) comes out of the box with Monolog.
+- [Laravel](http://laravel.com/) comes out of the box with Monolog.
+- [Lumen](http://lumen.laravel.com/) comes out of the box with Monolog.
+- [PPI](https://github.com/ppi/framework) comes out of the box with Monolog.
+- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
+- [XOOPS 2.6](http://xoops.org/) comes out of the box with Monolog.
+- [Aura.Web_Project](https://github.com/auraphp/Aura.Web_Project) comes out of the box with Monolog.
+- [Nette Framework](http://nette.org/en/) is usable with Monolog via the [contributte/monolog](https://github.com/contributte/monolog) or [orisai/nette-monolog](https://github.com/orisai/nette-monolog) extensions.
+- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.
+- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog.
+- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog.
+- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins.
+- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog.
+- [SilverStripe 4](https://www.silverstripe.org/) comes out of the box with Monolog.
+- [Drupal](https://www.drupal.org/) is usable with Monolog via the [monolog](https://www.drupal.org/project/monolog) module.
+- [Aimeos ecommerce framework](https://aimeos.org/) is usable with Monolog via the [ai-monolog](https://github.com/aimeos/ai-monolog) extension.
+- [Magento](https://magento.com/) comes out of the box with Monolog.
+- [Spiral Framework](https://spiral.dev) comes out of the box with Monolog bridge.
+- [WebFramework](https://web-framework.com/) comes out of the box with Monolog.
+
+### Author
+
+Jordi Boggiano - -
+See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) who participated in this project.
+
+### License
+
+Monolog is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
+
+### Acknowledgements
+
+This library is heavily inspired by Python's [Logbook](https://logbook.readthedocs.io/en/stable/)
+library, although most concepts have been adjusted to fit to the PHP world.
diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json
new file mode 100644
index 0000000..b0fb2d7
--- /dev/null
+++ b/vendor/monolog/monolog/composer.json
@@ -0,0 +1,82 @@
+{
+ "name": "monolog/monolog",
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "keywords": ["log", "logging", "psr-3"],
+ "homepage": "https://github.com/Seldaek/monolog",
+ "type": "library",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "https://seld.be"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^2.0 || ^3.0"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "aws/aws-sdk-php": "^3.0",
+ "doctrine/couchdb": "~1.0@dev",
+ "elasticsearch/elasticsearch": "^7 || ^8",
+ "graylog2/gelf-php": "^1.4.2 || ^2.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.2",
+ "mongodb/mongodb": "^1.8 || ^2.0",
+ "php-amqplib/php-amqplib": "~2.4 || ^3",
+ "php-console/php-console": "^3.1.8",
+ "phpstan/phpstan": "^2",
+ "phpstan/phpstan-deprecation-rules": "^2",
+ "phpstan/phpstan-strict-rules": "^2",
+ "phpunit/phpunit": "^10.5.17 || ^11.0.7",
+ "predis/predis": "^1.1 || ^2",
+ "rollbar/rollbar": "^4.0",
+ "ruflin/elastica": "^7 || ^8",
+ "symfony/mailer": "^5.4 || ^6",
+ "symfony/mime": "^5.4 || ^6"
+ },
+ "suggest": {
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
+ "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ext-mbstring": "Allow to work properly with unicode symbols",
+ "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
+ "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
+ "ext-openssl": "Required to send log messages using SSL"
+ },
+ "autoload": {
+ "psr-4": {"Monolog\\": "src/Monolog"}
+ },
+ "autoload-dev": {
+ "psr-4": {"Monolog\\": "tests/Monolog"}
+ },
+ "provide": {
+ "psr/log-implementation": "3.0.0"
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "scripts": {
+ "test": "@php vendor/bin/phpunit",
+ "phpstan": "@php vendor/bin/phpstan analyse"
+ },
+ "config": {
+ "lock": false,
+ "sort-packages": true,
+ "platform-check": false,
+ "allow-plugins": {
+ "php-http/discovery": false
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php
new file mode 100644
index 0000000..c519e05
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Attribute/AsMonologProcessor.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Attribute;
+
+/**
+ * A reusable attribute to help configure a class or a method as a processor.
+ *
+ * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.
+ *
+ * Using it with the Monolog library only has no effect at all: processors should still be turned into a callable if
+ * needed and manually pushed to the loggers and to the processable handlers.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class AsMonologProcessor
+{
+ /**
+ * @param string|null $channel The logging channel the processor should be pushed to.
+ * @param string|null $handler The handler the processor should be pushed to.
+ * @param string|null $method The method that processes the records (if the attribute is used at the class level).
+ * @param int|null $priority The priority of the processor so the order can be determined.
+ */
+ public function __construct(
+ public readonly ?string $channel = null,
+ public readonly ?string $handler = null,
+ public readonly ?string $method = null,
+ public readonly ?int $priority = null
+ ) {
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php b/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php
new file mode 100644
index 0000000..862e05b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Attribute/WithMonologChannel.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Attribute;
+
+/**
+ * A reusable attribute to help configure a class as expecting a given logger channel.
+ *
+ * Using it offers no guarantee: it needs to be leveraged by a Monolog third-party consumer.
+ *
+ * Using it with the Monolog library only has no effect at all: wiring the logger instance into
+ * other classes is not managed by Monolog.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+final class WithMonologChannel
+{
+ public function __construct(
+ public readonly string $channel
+ ) {
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php
new file mode 100644
index 0000000..3cb7086
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/DateTimeImmutable.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+class_alias(JsonSerializableDateTimeImmutable::class, 'Monolog\DateTimeImmutable');
+
+// @phpstan-ignore-next-line
+if (false) {
+ /**
+ * @deprecated Use \Monolog\JsonSerializableDateTimeImmutable instead.
+ */
+ class DateTimeImmutable extends JsonSerializableDateTimeImmutable
+ {
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php
new file mode 100644
index 0000000..805f2df
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php
@@ -0,0 +1,279 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog;
+
+use Closure;
+use Psr\Log\LoggerInterface;
+use Psr\Log\LogLevel;
+
+/**
+ * Monolog error handler
+ *
+ * A facility to enable logging of runtime errors, exceptions and fatal errors.
+ *
+ * Quick setup: ErrorHandler::register($logger);
+ *
+ * @author Jordi Boggiano
+ */
+class ErrorHandler
+{
+ private Closure|null $previousExceptionHandler = null;
+
+ /** @var array an array of class name to LogLevel::* constant mapping */
+ private array $uncaughtExceptionLevelMap = [];
+
+ /** @var Closure|true|null */
+ private Closure|bool|null $previousErrorHandler = null;
+
+ /** @var array an array of E_* constant to LogLevel::* constant mapping */
+ private array $errorLevelMap = [];
+
+ private bool $handleOnlyReportedErrors = true;
+
+ private bool $hasFatalErrorHandler = false;
+
+ private string $fatalLevel = LogLevel::ALERT;
+
+ private string|null $reservedMemory = null;
+
+ /** @var ?array{type: int, message: string, file: string, line: int, trace: mixed} */
+ private array|null $lastFatalData = null;
+
+ private const FATAL_ERRORS = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
+
+ public function __construct(
+ private LoggerInterface $logger
+ ) {
+ }
+
+ /**
+ * Registers a new ErrorHandler for a given Logger
+ *
+ * By default it will handle errors, exceptions and fatal errors
+ *
+ * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
+ * @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
+ * @param LogLevel::*|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
+ * @return static
+ */
+ public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self
+ {
+ /** @phpstan-ignore-next-line */
+ $handler = new static($logger);
+ if ($errorLevelMap !== false) {
+ $handler->registerErrorHandler($errorLevelMap);
+ }
+ if ($exceptionLevelMap !== false) {
+ $handler->registerExceptionHandler($exceptionLevelMap);
+ }
+ if ($fatalLevel !== false) {
+ $handler->registerFatalHandler($fatalLevel);
+ }
+
+ return $handler;
+ }
+
+ /**
+ * @param array $levelMap an array of class name to LogLevel::* constant mapping
+ * @return $this
+ */
+ public function registerExceptionHandler(array $levelMap = [], bool $callPrevious = true): self
+ {
+ $prev = set_exception_handler(function (\Throwable $e): void {
+ $this->handleException($e);
+ });
+ $this->uncaughtExceptionLevelMap = $levelMap;
+ foreach ($this->defaultExceptionLevelMap() as $class => $level) {
+ if (!isset($this->uncaughtExceptionLevelMap[$class])) {
+ $this->uncaughtExceptionLevelMap[$class] = $level;
+ }
+ }
+ if ($callPrevious && null !== $prev) {
+ $this->previousExceptionHandler = $prev(...);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array $levelMap an array of E_* constant to LogLevel::* constant mapping
+ * @return $this
+ */
+ public function registerErrorHandler(array $levelMap = [], bool $callPrevious = true, int $errorTypes = -1, bool $handleOnlyReportedErrors = true): self
+ {
+ $prev = set_error_handler($this->handleError(...), $errorTypes);
+ $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
+ if ($callPrevious) {
+ $this->previousErrorHandler = $prev !== null ? $prev(...) : true;
+ } else {
+ $this->previousErrorHandler = null;
+ }
+
+ $this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
+
+ return $this;
+ }
+
+ /**
+ * @param LogLevel::*|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT
+ * @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done
+ * @return $this
+ */
+ public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self
+ {
+ register_shutdown_function($this->handleFatalError(...));
+
+ $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
+ $this->fatalLevel = null === $level ? LogLevel::ALERT : $level;
+ $this->hasFatalErrorHandler = true;
+
+ return $this;
+ }
+
+ /**
+ * @return array
+ */
+ protected function defaultExceptionLevelMap(): array
+ {
+ return [
+ 'ParseError' => LogLevel::CRITICAL,
+ 'Throwable' => LogLevel::ERROR,
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ protected function defaultErrorLevelMap(): array
+ {
+ return [
+ E_ERROR => LogLevel::CRITICAL,
+ E_WARNING => LogLevel::WARNING,
+ E_PARSE => LogLevel::ALERT,
+ E_NOTICE => LogLevel::NOTICE,
+ E_CORE_ERROR => LogLevel::CRITICAL,
+ E_CORE_WARNING => LogLevel::WARNING,
+ E_COMPILE_ERROR => LogLevel::ALERT,
+ E_COMPILE_WARNING => LogLevel::WARNING,
+ E_USER_ERROR => LogLevel::ERROR,
+ E_USER_WARNING => LogLevel::WARNING,
+ E_USER_NOTICE => LogLevel::NOTICE,
+ 2048 => LogLevel::NOTICE, // E_STRICT
+ E_RECOVERABLE_ERROR => LogLevel::ERROR,
+ E_DEPRECATED => LogLevel::NOTICE,
+ E_USER_DEPRECATED => LogLevel::NOTICE,
+ ];
+ }
+
+ private function handleException(\Throwable $e): never
+ {
+ $level = LogLevel::ERROR;
+ foreach ($this->uncaughtExceptionLevelMap as $class => $candidate) {
+ if ($e instanceof $class) {
+ $level = $candidate;
+ break;
+ }
+ }
+
+ $this->logger->log(
+ $level,
+ sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
+ ['exception' => $e]
+ );
+
+ if (null !== $this->previousExceptionHandler) {
+ ($this->previousExceptionHandler)($e);
+ }
+
+ if (!headers_sent() && \in_array(strtolower((string) \ini_get('display_errors')), ['0', '', 'false', 'off', 'none', 'no'], true)) {
+ http_response_code(500);
+ }
+
+ exit(255);
+ }
+
+ private function handleError(int $code, string $message, string $file = '', int $line = 0): bool
+ {
+ if ($this->handleOnlyReportedErrors && 0 === (error_reporting() & $code)) {
+ return false;
+ }
+
+ // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
+ if (!$this->hasFatalErrorHandler || !\in_array($code, self::FATAL_ERRORS, true)) {
+ $level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
+ $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
+ } else {
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ array_shift($trace); // Exclude handleError from trace
+ $this->lastFatalData = ['type' => $code, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace];
+ }
+
+ if ($this->previousErrorHandler === true) {
+ return false;
+ }
+ if ($this->previousErrorHandler instanceof Closure) {
+ return (bool) ($this->previousErrorHandler)($code, $message, $file, $line);
+ }
+
+ return true;
+ }
+
+ /**
+ * @private
+ */
+ public function handleFatalError(): void
+ {
+ $this->reservedMemory = '';
+
+ if (\is_array($this->lastFatalData)) {
+ $lastError = $this->lastFatalData;
+ } else {
+ $lastError = error_get_last();
+ }
+ if (\is_array($lastError) && \in_array($lastError['type'], self::FATAL_ERRORS, true)) {
+ $trace = $lastError['trace'] ?? null;
+ $this->logger->log(
+ $this->fatalLevel,
+ 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
+ ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $trace]
+ );
+
+ if ($this->logger instanceof Logger) {
+ foreach ($this->logger->getHandlers() as $handler) {
+ $handler->close();
+ }
+ }
+ }
+ }
+
+ private static function codeToString(int $code): string
+ {
+ return match ($code) {
+ E_ERROR => 'E_ERROR',
+ E_WARNING => 'E_WARNING',
+ E_PARSE => 'E_PARSE',
+ E_NOTICE => 'E_NOTICE',
+ E_CORE_ERROR => 'E_CORE_ERROR',
+ E_CORE_WARNING => 'E_CORE_WARNING',
+ E_COMPILE_ERROR => 'E_COMPILE_ERROR',
+ E_COMPILE_WARNING => 'E_COMPILE_WARNING',
+ E_USER_ERROR => 'E_USER_ERROR',
+ E_USER_WARNING => 'E_USER_WARNING',
+ E_USER_NOTICE => 'E_USER_NOTICE',
+ 2048 => 'E_STRICT',
+ E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
+ E_DEPRECATED => 'E_DEPRECATED',
+ E_USER_DEPRECATED => 'E_USER_DEPRECATED',
+ default => 'Unknown PHP error',
+ };
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php
new file mode 100644
index 0000000..beb5106
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php
@@ -0,0 +1,87 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Formats a log message according to the ChromePHP array format
+ *
+ * @author Christophe Coevoet
+ */
+class ChromePHPFormatter implements FormatterInterface
+{
+ /**
+ * Translates Monolog log levels to Wildfire levels.
+ *
+ * @return 'log'|'info'|'warn'|'error'
+ */
+ private function toWildfireLevel(Level $level): string
+ {
+ return match ($level) {
+ Level::Debug => 'log',
+ Level::Info => 'info',
+ Level::Notice => 'info',
+ Level::Warning => 'warn',
+ Level::Error => 'error',
+ Level::Critical => 'error',
+ Level::Alert => 'error',
+ Level::Emergency => 'error',
+ };
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record)
+ {
+ // Retrieve the line and file if set and remove them from the formatted extra
+ $backtrace = 'unknown';
+ if (isset($record->extra['file'], $record->extra['line'])) {
+ $backtrace = $record->extra['file'].' : '.$record->extra['line'];
+ unset($record->extra['file'], $record->extra['line']);
+ }
+
+ $message = ['message' => $record->message];
+ if (\count($record->context) > 0) {
+ $message['context'] = $record->context;
+ }
+ if (\count($record->extra) > 0) {
+ $message['extra'] = $record->extra;
+ }
+ if (\count($message) === 1) {
+ $message = reset($message);
+ }
+
+ return [
+ $record->channel,
+ $message,
+ $backtrace,
+ $this->toWildfireLevel($record->level),
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function formatBatch(array $records)
+ {
+ $formatted = [];
+
+ foreach ($records as $record) {
+ $formatted[] = $this->format($record);
+ }
+
+ return $formatted;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php
new file mode 100644
index 0000000..a0fa4a9
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticaFormatter.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Elastica\Document;
+use Monolog\LogRecord;
+
+/**
+ * Format a log message into an Elastica Document
+ *
+ * @author Jelle Vink
+ */
+class ElasticaFormatter extends NormalizerFormatter
+{
+ /**
+ * @var string Elastic search index name
+ */
+ protected string $index;
+
+ /**
+ * @var string|null Elastic search document type
+ */
+ protected string|null $type;
+
+ /**
+ * @param string $index Elastic Search index name
+ * @param ?string $type Elastic Search document type, deprecated as of Elastica 7
+ */
+ public function __construct(string $index, ?string $type)
+ {
+ // elasticsearch requires a ISO 8601 format date with optional millisecond precision.
+ parent::__construct('Y-m-d\TH:i:s.uP');
+
+ $this->index = $index;
+ $this->type = $type;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record)
+ {
+ $record = parent::format($record);
+
+ return $this->getDocument($record);
+ }
+
+ public function getIndex(): string
+ {
+ return $this->index;
+ }
+
+ /**
+ * @deprecated since Elastica 7 type has no effect
+ */
+ public function getType(): string
+ {
+ /** @phpstan-ignore-next-line */
+ return $this->type;
+ }
+
+ /**
+ * Convert a log message into an Elastica Document
+ *
+ * @param mixed[] $record
+ */
+ protected function getDocument(array $record): Document
+ {
+ $document = new Document();
+ $document->setData($record);
+ $document->setIndex($this->index);
+
+ return $document;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php
new file mode 100644
index 0000000..6326cf5
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/ElasticsearchFormatter.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use DateTimeInterface;
+use Monolog\LogRecord;
+
+/**
+ * Format a log message into an Elasticsearch record
+ *
+ * @author Avtandil Kikabidze
+ */
+class ElasticsearchFormatter extends NormalizerFormatter
+{
+ /**
+ * @var string Elasticsearch index name
+ */
+ protected string $index;
+
+ /**
+ * @var string Elasticsearch record type
+ */
+ protected string $type;
+
+ /**
+ * @param string $index Elasticsearch index name
+ * @param string $type Elasticsearch record type
+ */
+ public function __construct(string $index, string $type)
+ {
+ // Elasticsearch requires an ISO 8601 format date with optional millisecond precision.
+ parent::__construct(DateTimeInterface::ATOM);
+
+ $this->index = $index;
+ $this->type = $type;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record)
+ {
+ $record = parent::format($record);
+
+ return $this->getDocument($record);
+ }
+
+ /**
+ * Getter index
+ */
+ public function getIndex(): string
+ {
+ return $this->index;
+ }
+
+ /**
+ * Getter type
+ */
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ /**
+ * Convert a log message into an Elasticsearch record
+ *
+ * @param mixed[] $record Log message
+ * @return mixed[]
+ */
+ protected function getDocument(array $record): array
+ {
+ $record['_index'] = $this->index;
+ $record['_type'] = $this->type;
+
+ return $record;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php
new file mode 100644
index 0000000..cc805c8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/FlowdockFormatter.php
@@ -0,0 +1,106 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * formats the record to be used in the FlowdockHandler
+ *
+ * @author Dominik Liebler
+ * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4
+ */
+class FlowdockFormatter implements FormatterInterface
+{
+ private string $source;
+
+ private string $sourceEmail;
+
+ public function __construct(string $source, string $sourceEmail)
+ {
+ $this->source = $source;
+ $this->sourceEmail = $sourceEmail;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return mixed[]
+ */
+ public function format(LogRecord $record): array
+ {
+ $tags = [
+ '#logs',
+ '#' . $record->level->toPsrLogLevel(),
+ '#' . $record->channel,
+ ];
+
+ foreach ($record->extra as $value) {
+ $tags[] = '#' . $value;
+ }
+
+ $subject = sprintf(
+ 'in %s: %s - %s',
+ $this->source,
+ $record->level->getName(),
+ $this->getShortMessage($record->message)
+ );
+
+ return [
+ 'source' => $this->source,
+ 'from_address' => $this->sourceEmail,
+ 'subject' => $subject,
+ 'content' => $record->message,
+ 'tags' => $tags,
+ 'project' => $this->source,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return mixed[][]
+ */
+ public function formatBatch(array $records): array
+ {
+ $formatted = [];
+
+ foreach ($records as $record) {
+ $formatted[] = $this->format($record);
+ }
+
+ return $formatted;
+ }
+
+ public function getShortMessage(string $message): string
+ {
+ static $hasMbString;
+
+ if (null === $hasMbString) {
+ $hasMbString = \function_exists('mb_strlen');
+ }
+
+ $maxLength = 45;
+
+ if ($hasMbString) {
+ if (mb_strlen($message, 'UTF-8') > $maxLength) {
+ $message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
+ }
+ } else {
+ if (\strlen($message) > $maxLength) {
+ $message = substr($message, 0, $maxLength - 4) . ' ...';
+ }
+ }
+
+ return $message;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php
new file mode 100644
index 0000000..5f78117
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/FluentdFormatter.php
@@ -0,0 +1,85 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Class FluentdFormatter
+ *
+ * Serializes a log message to Fluentd unix socket protocol
+ *
+ * Fluentd config:
+ *
+ *
+ * type unix
+ * path /var/run/td-agent/td-agent.sock
+ *
+ *
+ * Monolog setup:
+ *
+ * $logger = new Monolog\Logger('fluent.tag');
+ * $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
+ * $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
+ * $logger->pushHandler($fluentHandler);
+ *
+ * @author Andrius Putna
+ */
+class FluentdFormatter implements FormatterInterface
+{
+ /**
+ * @var bool $levelTag should message level be a part of the fluentd tag
+ */
+ protected bool $levelTag = false;
+
+ public function __construct(bool $levelTag = false)
+ {
+ $this->levelTag = $levelTag;
+ }
+
+ public function isUsingLevelsInTag(): bool
+ {
+ return $this->levelTag;
+ }
+
+ public function format(LogRecord $record): string
+ {
+ $tag = $record->channel;
+ if ($this->levelTag) {
+ $tag .= '.' . $record->level->toPsrLogLevel();
+ }
+
+ $message = [
+ 'message' => $record->message,
+ 'context' => $record->context,
+ 'extra' => $record->extra,
+ ];
+
+ if (!$this->levelTag) {
+ $message['level'] = $record->level->value;
+ $message['level_name'] = $record->level->getName();
+ }
+
+ return Utils::jsonEncode([$tag, $record->datetime->getTimestamp(), $message]);
+ }
+
+ public function formatBatch(array $records): string
+ {
+ $message = '';
+ foreach ($records as $record) {
+ $message .= $this->format($record);
+ }
+
+ return $message;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php
new file mode 100644
index 0000000..3413a4b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * Interface for formatters
+ *
+ * @author Jordi Boggiano
+ */
+interface FormatterInterface
+{
+ /**
+ * Formats a log record.
+ *
+ * @param LogRecord $record A record to format
+ * @return mixed The formatted record
+ */
+ public function format(LogRecord $record);
+
+ /**
+ * Formats a set of log records.
+ *
+ * @param array $records A set of records to format
+ * @return mixed The formatted set of records
+ */
+ public function formatBatch(array $records);
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php
new file mode 100644
index 0000000..bf8ed24
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php
@@ -0,0 +1,152 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Level;
+use Gelf\Message;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Serializes a log message to GELF
+ * @see http://docs.graylog.org/en/latest/pages/gelf.html
+ *
+ * @author Matt Lehner
+ */
+class GelfMessageFormatter extends NormalizerFormatter
+{
+ protected const DEFAULT_MAX_LENGTH = 32766;
+
+ /**
+ * @var string the name of the system for the Gelf log message
+ */
+ protected string $systemName;
+
+ /**
+ * @var string a prefix for 'extra' fields from the Monolog record (optional)
+ */
+ protected string $extraPrefix;
+
+ /**
+ * @var string a prefix for 'context' fields from the Monolog record (optional)
+ */
+ protected string $contextPrefix;
+
+ /**
+ * @var int max length per field
+ */
+ protected int $maxLength;
+
+ /**
+ * Translates Monolog log levels to Graylog2 log priorities.
+ */
+ private function getGraylog2Priority(Level $level): int
+ {
+ return match ($level) {
+ Level::Debug => 7,
+ Level::Info => 6,
+ Level::Notice => 5,
+ Level::Warning => 4,
+ Level::Error => 3,
+ Level::Critical => 2,
+ Level::Alert => 1,
+ Level::Emergency => 0,
+ };
+ }
+
+ /**
+ * @throws \RuntimeException
+ */
+ public function __construct(?string $systemName = null, ?string $extraPrefix = null, string $contextPrefix = 'ctxt_', ?int $maxLength = null)
+ {
+ if (!class_exists(Message::class)) {
+ throw new \RuntimeException('Composer package graylog2/gelf-php is required to use Monolog\'s GelfMessageFormatter');
+ }
+
+ parent::__construct('U.u');
+
+ $this->systemName = (null === $systemName || $systemName === '') ? (string) gethostname() : $systemName;
+
+ $this->extraPrefix = null === $extraPrefix ? '' : $extraPrefix;
+ $this->contextPrefix = $contextPrefix;
+ $this->maxLength = null === $maxLength ? self::DEFAULT_MAX_LENGTH : $maxLength;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record): Message
+ {
+ $context = $extra = [];
+ if (isset($record->context)) {
+ /** @var array|bool|float|int|string|null> $context */
+ $context = parent::normalize($record->context);
+ }
+ if (isset($record->extra)) {
+ /** @var array|bool|float|int|string|null> $extra */
+ $extra = parent::normalize($record->extra);
+ }
+
+ $message = new Message();
+ $message
+ ->setTimestamp($record->datetime)
+ ->setShortMessage($record->message)
+ ->setHost($this->systemName)
+ ->setLevel($this->getGraylog2Priority($record->level));
+
+ // message length + system name length + 200 for padding / metadata
+ $len = 200 + \strlen($record->message) + \strlen($this->systemName);
+
+ if ($len > $this->maxLength) {
+ $message->setShortMessage(Utils::substr($record->message, 0, $this->maxLength));
+ }
+
+ if (isset($record->channel)) {
+ $message->setAdditional('facility', $record->channel);
+ }
+
+ foreach ($extra as $key => $val) {
+ $key = (string) preg_replace('#[^\w.-]#', '-', (string) $key);
+ $val = \is_bool($val) ? ($val ? 1 : 0) : $val;
+ $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val);
+ $len = \strlen($this->extraPrefix . $key . $val);
+ if ($len > $this->maxLength) {
+ $message->setAdditional($this->extraPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));
+
+ continue;
+ }
+ $message->setAdditional($this->extraPrefix . $key, $val);
+ }
+
+ foreach ($context as $key => $val) {
+ $key = (string) preg_replace('#[^\w.-]#', '-', (string) $key);
+ $val = \is_bool($val) ? ($val ? 1 : 0) : $val;
+ $val = \is_scalar($val) || null === $val ? $val : $this->toJson($val);
+ $len = \strlen($this->contextPrefix . $key . $val);
+ if ($len > $this->maxLength) {
+ $message->setAdditional($this->contextPrefix . $key, Utils::substr((string) $val, 0, $this->maxLength));
+
+ continue;
+ }
+ $message->setAdditional($this->contextPrefix . $key, $val);
+ }
+
+ if (!$message->hasAdditional('file') && isset($context['exception']['file'])) {
+ if (1 === preg_match("/^(.+):([0-9]+)$/", $context['exception']['file'], $matches)) {
+ $message->setAdditional('file', $matches[1]);
+ $message->setAdditional('line', $matches[2]);
+ }
+ }
+
+ return $message;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php
new file mode 100644
index 0000000..c97b912
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/GoogleCloudLoggingFormatter.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use DateTimeInterface;
+use Monolog\LogRecord;
+
+/**
+ * Encodes message information into JSON in a format compatible with Cloud logging.
+ *
+ * @see https://cloud.google.com/logging/docs/structured-logging
+ * @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
+ *
+ * @author Luís Cobucci
+ */
+class GoogleCloudLoggingFormatter extends JsonFormatter
+{
+ protected function normalizeRecord(LogRecord $record): array
+ {
+ $normalized = parent::normalizeRecord($record);
+
+ // Re-key level for GCP logging
+ $normalized['severity'] = $normalized['level_name'];
+ $normalized['time'] = $record->datetime->format(DateTimeInterface::RFC3339_EXTENDED);
+
+ // Remove keys that are not used by GCP
+ unset($normalized['level'], $normalized['level_name'], $normalized['datetime']);
+
+ return $normalized;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php
new file mode 100644
index 0000000..09cbea9
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/HtmlFormatter.php
@@ -0,0 +1,142 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Formats incoming records into an HTML table
+ *
+ * This is especially useful for html email logging
+ *
+ * @author Tiago Brito
+ */
+class HtmlFormatter extends NormalizerFormatter
+{
+ /**
+ * Translates Monolog log levels to html color priorities.
+ */
+ protected function getLevelColor(Level $level): string
+ {
+ return match ($level) {
+ Level::Debug => '#CCCCCC',
+ Level::Info => '#28A745',
+ Level::Notice => '#17A2B8',
+ Level::Warning => '#FFC107',
+ Level::Error => '#FD7E14',
+ Level::Critical => '#DC3545',
+ Level::Alert => '#821722',
+ Level::Emergency => '#000000',
+ };
+ }
+
+ /**
+ * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
+ */
+ public function __construct(?string $dateFormat = null)
+ {
+ parent::__construct($dateFormat);
+ }
+
+ /**
+ * Creates an HTML table row
+ *
+ * @param string $th Row header content
+ * @param string $td Row standard cell content
+ * @param bool $escapeTd false if td content must not be html escaped
+ */
+ protected function addRow(string $th, string $td = ' ', bool $escapeTd = true): string
+ {
+ $th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
+ if ($escapeTd) {
+ $td = ''.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').' ';
+ }
+
+ return "\n$th: \n".$td." \n ";
+ }
+
+ /**
+ * Create a HTML h1 tag
+ *
+ * @param string $title Text to be in the h1
+ */
+ protected function addTitle(string $title, Level $level): string
+ {
+ $title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
+
+ return ''.$title.' ';
+ }
+
+ /**
+ * Formats a log record.
+ *
+ * @return string The formatted record
+ */
+ public function format(LogRecord $record): string
+ {
+ $output = $this->addTitle($record->level->getName(), $record->level);
+ $output .= '';
+
+ $output .= $this->addRow('Message', $record->message);
+ $output .= $this->addRow('Time', $this->formatDate($record->datetime));
+ $output .= $this->addRow('Channel', $record->channel);
+ if (\count($record->context) > 0) {
+ $embeddedTable = '';
+ foreach ($record->context as $key => $value) {
+ $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
+ }
+ $embeddedTable .= '
';
+ $output .= $this->addRow('Context', $embeddedTable, false);
+ }
+ if (\count($record->extra) > 0) {
+ $embeddedTable = '';
+ foreach ($record->extra as $key => $value) {
+ $embeddedTable .= $this->addRow((string) $key, $this->convertToString($value));
+ }
+ $embeddedTable .= '
';
+ $output .= $this->addRow('Extra', $embeddedTable, false);
+ }
+
+ return $output.'
';
+ }
+
+ /**
+ * Formats a set of log records.
+ *
+ * @return string The formatted set of records
+ */
+ public function formatBatch(array $records): string
+ {
+ $message = '';
+ foreach ($records as $record) {
+ $message .= $this->format($record);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param mixed $data
+ */
+ protected function convertToString($data): string
+ {
+ if (null === $data || \is_scalar($data)) {
+ return (string) $data;
+ }
+
+ $data = $this->normalize($data);
+
+ return Utils::jsonEncode($data, JSON_PRETTY_PRINT | Utils::DEFAULT_JSON_FLAGS, true);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php
new file mode 100644
index 0000000..fa11d0e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php
@@ -0,0 +1,234 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Stringable;
+use Throwable;
+use Monolog\LogRecord;
+
+/**
+ * Encodes whatever record data is passed to it as json
+ *
+ * This can be useful to log to databases or remote APIs
+ *
+ * @author Jordi Boggiano
+ */
+class JsonFormatter extends NormalizerFormatter
+{
+ public const BATCH_MODE_JSON = 1;
+ public const BATCH_MODE_NEWLINES = 2;
+
+ /** @var self::BATCH_MODE_* */
+ protected int $batchMode;
+
+ protected bool $appendNewline;
+
+ protected bool $ignoreEmptyContextAndExtra;
+
+ protected bool $includeStacktraces = false;
+
+ /**
+ * @param self::BATCH_MODE_* $batchMode
+ */
+ public function __construct(int $batchMode = self::BATCH_MODE_JSON, bool $appendNewline = true, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)
+ {
+ $this->batchMode = $batchMode;
+ $this->appendNewline = $appendNewline;
+ $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
+ $this->includeStacktraces = $includeStacktraces;
+
+ parent::__construct();
+ }
+
+ /**
+ * The batch mode option configures the formatting style for
+ * multiple records. By default, multiple records will be
+ * formatted as a JSON-encoded array. However, for
+ * compatibility with some API endpoints, alternative styles
+ * are available.
+ */
+ public function getBatchMode(): int
+ {
+ return $this->batchMode;
+ }
+
+ /**
+ * True if newlines are appended to every formatted record
+ */
+ public function isAppendingNewlines(): bool
+ {
+ return $this->appendNewline;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record): string
+ {
+ $normalized = $this->normalizeRecord($record);
+
+ return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : '');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function formatBatch(array $records): string
+ {
+ return match ($this->batchMode) {
+ static::BATCH_MODE_NEWLINES => $this->formatBatchNewlines($records),
+ default => $this->formatBatchJson($records),
+ };
+ }
+
+ /**
+ * @return $this
+ */
+ public function includeStacktraces(bool $include = true): self
+ {
+ $this->includeStacktraces = $include;
+
+ return $this;
+ }
+
+ /**
+ * @return array|bool|float|int|\stdClass|string|null>
+ */
+ protected function normalizeRecord(LogRecord $record): array
+ {
+ $normalized = parent::normalizeRecord($record);
+
+ if (isset($normalized['context']) && $normalized['context'] === []) {
+ if ($this->ignoreEmptyContextAndExtra) {
+ unset($normalized['context']);
+ } else {
+ $normalized['context'] = new \stdClass;
+ }
+ }
+ if (isset($normalized['extra']) && $normalized['extra'] === []) {
+ if ($this->ignoreEmptyContextAndExtra) {
+ unset($normalized['extra']);
+ } else {
+ $normalized['extra'] = new \stdClass;
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Return a JSON-encoded array of records.
+ *
+ * @phpstan-param LogRecord[] $records
+ */
+ protected function formatBatchJson(array $records): string
+ {
+ $formatted = array_map(fn (LogRecord $record) => $this->normalizeRecord($record), $records);
+
+ return $this->toJson($formatted, true);
+ }
+
+ /**
+ * Use new lines to separate records instead of a
+ * JSON-encoded array.
+ *
+ * @phpstan-param LogRecord[] $records
+ */
+ protected function formatBatchNewlines(array $records): string
+ {
+ $oldNewline = $this->appendNewline;
+ $this->appendNewline = false;
+ $formatted = array_map(fn (LogRecord $record) => $this->format($record), $records);
+ $this->appendNewline = $oldNewline;
+
+ return implode("\n", $formatted);
+ }
+
+ /**
+ * Normalizes given $data.
+ *
+ * @return null|scalar|array|object
+ */
+ protected function normalize(mixed $data, int $depth = 0): mixed
+ {
+ if ($depth > $this->maxNormalizeDepth) {
+ return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
+ }
+
+ if (\is_array($data)) {
+ $normalized = [];
+
+ $count = 1;
+ foreach ($data as $key => $value) {
+ if ($count++ > $this->maxNormalizeItemCount) {
+ $normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.\count($data).' total), aborting normalization';
+ break;
+ }
+
+ $normalized[$key] = $this->normalize($value, $depth + 1);
+ }
+
+ return $normalized;
+ }
+
+ if (\is_object($data)) {
+ if ($data instanceof \DateTimeInterface) {
+ return $this->formatDate($data);
+ }
+
+ if ($data instanceof Throwable) {
+ return $this->normalizeException($data, $depth);
+ }
+
+ // if the object has specific json serializability we want to make sure we skip the __toString treatment below
+ if ($data instanceof \JsonSerializable) {
+ return $data;
+ }
+
+ if ($data instanceof Stringable) {
+ try {
+ return $data->__toString();
+ } catch (Throwable) {
+ return $data::class;
+ }
+ }
+
+ if (\get_class($data) === '__PHP_Incomplete_Class') {
+ return new \ArrayObject($data);
+ }
+
+ return $data;
+ }
+
+ if (\is_resource($data)) {
+ return parent::normalize($data);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Normalizes given exception with or without its own stack trace based on
+ * `includeStacktraces` property.
+ *
+ * @return array>>
+ */
+ protected function normalizeException(Throwable $e, int $depth = 0): array
+ {
+ $data = parent::normalizeException($e, $depth);
+ if (!$this->includeStacktraces) {
+ unset($data['trace']);
+ }
+
+ return $data;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php
new file mode 100644
index 0000000..d44e7ae
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php
@@ -0,0 +1,317 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Closure;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Formats incoming records into a one-line string
+ *
+ * This is especially useful for logging to files
+ *
+ * @author Jordi Boggiano
+ * @author Christophe Coevoet
+ */
+class LineFormatter extends NormalizerFormatter
+{
+ public const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
+
+ protected string $format;
+ protected bool $allowInlineLineBreaks;
+ protected bool $ignoreEmptyContextAndExtra;
+ protected bool $includeStacktraces;
+ protected ?int $maxLevelNameLength = null;
+ protected string $indentStacktraces = '';
+ protected Closure|null $stacktracesParser = null;
+ protected string $basePath = '';
+
+ /**
+ * @param string|null $format The format of the message
+ * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
+ * @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
+ */
+ public function __construct(?string $format = null, ?string $dateFormat = null, bool $allowInlineLineBreaks = false, bool $ignoreEmptyContextAndExtra = false, bool $includeStacktraces = false)
+ {
+ $this->format = $format === null ? static::SIMPLE_FORMAT : $format;
+ $this->allowInlineLineBreaks = $allowInlineLineBreaks;
+ $this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
+ $this->includeStacktraces($includeStacktraces);
+ parent::__construct($dateFormat);
+ }
+
+ /**
+ * Setting a base path will hide the base path from exception and stack trace file names to shorten them
+ * @return $this
+ */
+ public function setBasePath(string $path = ''): self
+ {
+ if ($path !== '') {
+ $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ }
+
+ $this->basePath = $path;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function includeStacktraces(bool $include = true, ?Closure $parser = null): self
+ {
+ $this->includeStacktraces = $include;
+ if ($this->includeStacktraces) {
+ $this->allowInlineLineBreaks = true;
+ $this->stacktracesParser = $parser;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Indent stack traces to separate them a bit from the main log record messages
+ *
+ * @param string $indent The string used to indent, for example " "
+ * @return $this
+ */
+ public function indentStacktraces(string $indent): self
+ {
+ $this->indentStacktraces = $indent;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function allowInlineLineBreaks(bool $allow = true): self
+ {
+ $this->allowInlineLineBreaks = $allow;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function ignoreEmptyContextAndExtra(bool $ignore = true): self
+ {
+ $this->ignoreEmptyContextAndExtra = $ignore;
+
+ return $this;
+ }
+
+ /**
+ * Allows cutting the level name to get fixed-length levels like INF for INFO, ERR for ERROR if you set this to 3 for example
+ *
+ * @param int|null $maxLevelNameLength Maximum characters for the level name. Set null for infinite length (default)
+ * @return $this
+ */
+ public function setMaxLevelNameLength(?int $maxLevelNameLength = null): self
+ {
+ $this->maxLevelNameLength = $maxLevelNameLength;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record): string
+ {
+ $vars = parent::format($record);
+
+ if ($this->maxLevelNameLength !== null) {
+ $vars['level_name'] = substr($vars['level_name'], 0, $this->maxLevelNameLength);
+ }
+
+ $output = $this->format;
+ foreach ($vars['extra'] as $var => $val) {
+ if (false !== strpos($output, '%extra.'.$var.'%')) {
+ $output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
+ unset($vars['extra'][$var]);
+ }
+ }
+
+ foreach ($vars['context'] as $var => $val) {
+ if (false !== strpos($output, '%context.'.$var.'%')) {
+ $output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
+ unset($vars['context'][$var]);
+ }
+ }
+
+ if ($this->ignoreEmptyContextAndExtra) {
+ if (\count($vars['context']) === 0) {
+ unset($vars['context']);
+ $output = str_replace('%context%', '', $output);
+ }
+
+ if (\count($vars['extra']) === 0) {
+ unset($vars['extra']);
+ $output = str_replace('%extra%', '', $output);
+ }
+ }
+
+ foreach ($vars as $var => $val) {
+ if (false !== strpos($output, '%'.$var.'%')) {
+ $output = str_replace('%'.$var.'%', $this->stringify($val), $output);
+ }
+ }
+
+ // remove leftover %extra.xxx% and %context.xxx% if any
+ if (false !== strpos($output, '%')) {
+ $output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
+ if (null === $output) {
+ $pcreErrorCode = preg_last_error();
+
+ throw new \RuntimeException('Failed to run preg_replace: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());
+ }
+ }
+
+ return $output;
+ }
+
+ public function formatBatch(array $records): string
+ {
+ $message = '';
+ foreach ($records as $record) {
+ $message .= $this->format($record);
+ }
+
+ return $message;
+ }
+
+ /**
+ * @param mixed $value
+ */
+ public function stringify($value): string
+ {
+ return $this->replaceNewlines($this->convertToString($value));
+ }
+
+ protected function normalizeException(\Throwable $e, int $depth = 0): string
+ {
+ $str = $this->formatException($e);
+
+ $previous = $e->getPrevious();
+ while ($previous instanceof \Throwable) {
+ $depth++;
+ if ($depth > $this->maxNormalizeDepth) {
+ $str .= "\n[previous exception] Over " . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
+ break;
+ }
+ $str .= "\n[previous exception] " . $this->formatException($previous);
+ $previous = $previous->getPrevious();
+ }
+
+ return $str;
+ }
+
+ /**
+ * @param mixed $data
+ */
+ protected function convertToString($data): string
+ {
+ if (null === $data || \is_bool($data)) {
+ return var_export($data, true);
+ }
+
+ if (\is_scalar($data)) {
+ return (string) $data;
+ }
+
+ return $this->toJson($data, true);
+ }
+
+ protected function replaceNewlines(string $str): string
+ {
+ if ($this->allowInlineLineBreaks) {
+ if (0 === strpos($str, '{') || 0 === strpos($str, '[')) {
+ $str = preg_replace('/(?getCode();
+ if ($e instanceof \SoapFault) {
+ if (isset($e->faultcode)) {
+ $str .= ' faultcode: ' . $e->faultcode;
+ }
+
+ if (isset($e->faultactor)) {
+ $str .= ' faultactor: ' . $e->faultactor;
+ }
+
+ if (isset($e->detail)) {
+ if (\is_string($e->detail)) {
+ $str .= ' detail: ' . $e->detail;
+ } elseif (\is_object($e->detail) || \is_array($e->detail)) {
+ $str .= ' detail: ' . $this->toJson($e->detail, true);
+ }
+ }
+ }
+
+ $file = $e->getFile();
+ if ($this->basePath !== '') {
+ $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);
+ }
+
+ $str .= '): ' . $e->getMessage() . ' at ' . strtr((string) $file, DIRECTORY_SEPARATOR, '/') . ':' . $e->getLine() . ')';
+
+ if ($this->includeStacktraces) {
+ $str .= $this->stacktracesParser($e);
+ }
+
+ return $str;
+ }
+
+ private function stacktracesParser(\Throwable $e): string
+ {
+ $trace = $e->getTraceAsString();
+
+ if ($this->basePath !== '') {
+ $trace = preg_replace('{^(#\d+ )' . preg_quote($this->basePath) . '}m', '$1', $trace) ?? $trace;
+ }
+
+ if ($this->stacktracesParser !== null) {
+ $trace = $this->stacktracesParserCustom($trace);
+ }
+
+ if ($this->indentStacktraces !== '') {
+ $trace = str_replace("\n", "\n{$this->indentStacktraces}", $trace);
+ }
+
+ if (trim($trace) === '') {
+ return '';
+ }
+
+ return "\n{$this->indentStacktraces}[stacktrace]\n{$this->indentStacktraces}" . strtr($trace, DIRECTORY_SEPARATOR, '/') . "\n";
+ }
+
+ private function stacktracesParserCustom(string $trace): string
+ {
+ return implode("\n", array_filter(array_map($this->stacktracesParser, explode("\n", $trace)), fn ($line) => is_string($line) && trim($line) !== ''));
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php
new file mode 100644
index 0000000..5f0b6a4
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogglyFormatter.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * Encodes message information into JSON in a format compatible with Loggly.
+ *
+ * @author Adam Pancutt
+ */
+class LogglyFormatter extends JsonFormatter
+{
+ /**
+ * Overrides the default batch mode to new lines for compatibility with the
+ * Loggly bulk API.
+ */
+ public function __construct(int $batchMode = self::BATCH_MODE_NEWLINES, bool $appendNewline = false)
+ {
+ parent::__construct($batchMode, $appendNewline);
+ }
+
+ /**
+ * Appends the 'timestamp' parameter for indexing by Loggly.
+ *
+ * @see https://www.loggly.com/docs/automated-parsing/#json
+ * @see \Monolog\Formatter\JsonFormatter::format()
+ */
+ protected function normalizeRecord(LogRecord $record): array
+ {
+ $recordData = parent::normalizeRecord($record);
+
+ $recordData["timestamp"] = $record->datetime->format("Y-m-d\TH:i:s.uO");
+ unset($recordData["datetime"]);
+
+ return $recordData;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php
new file mode 100644
index 0000000..9e44c19
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogmaticFormatter.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * Encodes message information into JSON in a format compatible with Logmatic.
+ *
+ * @author Julien Breux
+ */
+class LogmaticFormatter extends JsonFormatter
+{
+ protected const MARKERS = ["sourcecode", "php"];
+
+ protected string $hostname = '';
+
+ protected string $appName = '';
+
+ /**
+ * @return $this
+ */
+ public function setHostname(string $hostname): self
+ {
+ $this->hostname = $hostname;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setAppName(string $appName): self
+ {
+ $this->appName = $appName;
+
+ return $this;
+ }
+
+ /**
+ * Appends the 'hostname' and 'appname' parameter for indexing by Logmatic.
+ *
+ * @see http://doc.logmatic.io/docs/basics-to-send-data
+ * @see \Monolog\Formatter\JsonFormatter::format()
+ */
+ public function normalizeRecord(LogRecord $record): array
+ {
+ $record = parent::normalizeRecord($record);
+
+ if ($this->hostname !== '') {
+ $record["hostname"] = $this->hostname;
+ }
+ if ($this->appName !== '') {
+ $record["appname"] = $this->appName;
+ }
+
+ $record["@marker"] = static::MARKERS;
+
+ return $record;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php
new file mode 100644
index 0000000..d0e8749
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * Serializes a log message to Logstash Event Format
+ *
+ * @see https://www.elastic.co/products/logstash
+ * @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java
+ *
+ * @author Tim Mower
+ */
+class LogstashFormatter extends NormalizerFormatter
+{
+ /**
+ * @var string the name of the system for the Logstash log message, used to fill the @source field
+ */
+ protected string $systemName;
+
+ /**
+ * @var string an application name for the Logstash log message, used to fill the @type field
+ */
+ protected string $applicationName;
+
+ /**
+ * @var string the key for 'extra' fields from the Monolog record
+ */
+ protected string $extraKey;
+
+ /**
+ * @var string the key for 'context' fields from the Monolog record
+ */
+ protected string $contextKey;
+
+ /**
+ * @param string $applicationName The application that sends the data, used as the "type" field of logstash
+ * @param string|null $systemName The system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
+ * @param string $extraKey The key for extra keys inside logstash "fields", defaults to extra
+ * @param string $contextKey The key for context keys inside logstash "fields", defaults to context
+ */
+ public function __construct(string $applicationName, ?string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')
+ {
+ // logstash requires a ISO 8601 format date with optional millisecond precision.
+ parent::__construct('Y-m-d\TH:i:s.uP');
+
+ $this->systemName = $systemName === null ? (string) gethostname() : $systemName;
+ $this->applicationName = $applicationName;
+ $this->extraKey = $extraKey;
+ $this->contextKey = $contextKey;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record): string
+ {
+ $recordData = parent::format($record);
+
+ $message = [
+ '@timestamp' => $recordData['datetime'],
+ '@version' => 1,
+ 'host' => $this->systemName,
+ ];
+ if (isset($recordData['message'])) {
+ $message['message'] = $recordData['message'];
+ }
+ if (isset($recordData['channel'])) {
+ $message['type'] = $recordData['channel'];
+ $message['channel'] = $recordData['channel'];
+ }
+ if (isset($recordData['level_name'])) {
+ $message['level'] = $recordData['level_name'];
+ }
+ if (isset($recordData['level'])) {
+ $message['monolog_level'] = $recordData['level'];
+ }
+ if ('' !== $this->applicationName) {
+ $message['type'] = $this->applicationName;
+ }
+ if (\count($recordData['extra']) > 0) {
+ $message[$this->extraKey] = $recordData['extra'];
+ }
+ if (\count($recordData['context']) > 0) {
+ $message[$this->contextKey] = $recordData['context'];
+ }
+
+ return $this->toJson($message) . "\n";
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
new file mode 100644
index 0000000..64ecef2
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/MongoDBFormatter.php
@@ -0,0 +1,129 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use MongoDB\BSON\Type;
+use MongoDB\BSON\UTCDateTime;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Formats a record for use with the MongoDBHandler.
+ *
+ * @author Florian Plattner
+ */
+class MongoDBFormatter implements FormatterInterface
+{
+ private bool $exceptionTraceAsString;
+ private int $maxNestingLevel;
+
+ /**
+ * @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record->context is 2
+ * @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
+ */
+ public function __construct(int $maxNestingLevel = 3, bool $exceptionTraceAsString = true)
+ {
+ $this->maxNestingLevel = max($maxNestingLevel, 0);
+ $this->exceptionTraceAsString = $exceptionTraceAsString;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return mixed[]
+ */
+ public function format(LogRecord $record): array
+ {
+ /** @var mixed[] $res */
+ $res = $this->formatArray($record->toArray());
+
+ return $res;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return array
+ */
+ public function formatBatch(array $records): array
+ {
+ $formatted = [];
+ foreach ($records as $key => $record) {
+ $formatted[$key] = $this->format($record);
+ }
+
+ return $formatted;
+ }
+
+ /**
+ * @param mixed[] $array
+ * @return mixed[]|string Array except when max nesting level is reached then a string "[...]"
+ */
+ protected function formatArray(array $array, int $nestingLevel = 0)
+ {
+ if ($this->maxNestingLevel > 0 && $nestingLevel > $this->maxNestingLevel) {
+ return '[...]';
+ }
+
+ foreach ($array as $name => $value) {
+ if ($value instanceof \DateTimeInterface) {
+ $array[$name] = $this->formatDate($value, $nestingLevel + 1);
+ } elseif ($value instanceof \Throwable) {
+ $array[$name] = $this->formatException($value, $nestingLevel + 1);
+ } elseif (\is_array($value)) {
+ $array[$name] = $this->formatArray($value, $nestingLevel + 1);
+ } elseif (\is_object($value) && !$value instanceof Type) {
+ $array[$name] = $this->formatObject($value, $nestingLevel + 1);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * @param mixed $value
+ * @return mixed[]|string
+ */
+ protected function formatObject($value, int $nestingLevel)
+ {
+ $objectVars = get_object_vars($value);
+ $objectVars['class'] = Utils::getClass($value);
+
+ return $this->formatArray($objectVars, $nestingLevel);
+ }
+
+ /**
+ * @return mixed[]|string
+ */
+ protected function formatException(\Throwable $exception, int $nestingLevel)
+ {
+ $formattedException = [
+ 'class' => Utils::getClass($exception),
+ 'message' => $exception->getMessage(),
+ 'code' => (int) $exception->getCode(),
+ 'file' => $exception->getFile() . ':' . $exception->getLine(),
+ ];
+
+ if ($this->exceptionTraceAsString === true) {
+ $formattedException['trace'] = $exception->getTraceAsString();
+ } else {
+ $formattedException['trace'] = $exception->getTrace();
+ }
+
+ return $this->formatArray($formattedException, $nestingLevel);
+ }
+
+ protected function formatDate(\DateTimeInterface $value, int $nestingLevel): UTCDateTime
+ {
+ return new UTCDateTime((int) floor(((float) $value->format('U.u')) * 1000));
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php
new file mode 100644
index 0000000..60da29c
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php
@@ -0,0 +1,353 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\JsonSerializableDateTimeImmutable;
+use Monolog\Utils;
+use Throwable;
+use Monolog\LogRecord;
+
+/**
+ * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
+ *
+ * @author Jordi Boggiano
+ */
+class NormalizerFormatter implements FormatterInterface
+{
+ public const SIMPLE_DATE = "Y-m-d\TH:i:sP";
+
+ protected string $dateFormat;
+ protected int $maxNormalizeDepth = 9;
+ protected int $maxNormalizeItemCount = 1000;
+
+ private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
+
+ protected string $basePath = '';
+
+ /**
+ * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
+ */
+ public function __construct(?string $dateFormat = null)
+ {
+ $this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record)
+ {
+ return $this->normalizeRecord($record);
+ }
+
+ /**
+ * Normalize an arbitrary value to a scalar|array|null
+ *
+ * @return null|scalar|array
+ */
+ public function normalizeValue(mixed $data): mixed
+ {
+ return $this->normalize($data);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function formatBatch(array $records)
+ {
+ foreach ($records as $key => $record) {
+ $records[$key] = $this->format($record);
+ }
+
+ return $records;
+ }
+
+ public function getDateFormat(): string
+ {
+ return $this->dateFormat;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setDateFormat(string $dateFormat): self
+ {
+ $this->dateFormat = $dateFormat;
+
+ return $this;
+ }
+
+ /**
+ * The maximum number of normalization levels to go through
+ */
+ public function getMaxNormalizeDepth(): int
+ {
+ return $this->maxNormalizeDepth;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setMaxNormalizeDepth(int $maxNormalizeDepth): self
+ {
+ $this->maxNormalizeDepth = $maxNormalizeDepth;
+
+ return $this;
+ }
+
+ /**
+ * The maximum number of items to normalize per level
+ */
+ public function getMaxNormalizeItemCount(): int
+ {
+ return $this->maxNormalizeItemCount;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self
+ {
+ $this->maxNormalizeItemCount = $maxNormalizeItemCount;
+
+ return $this;
+ }
+
+ /**
+ * Enables `json_encode` pretty print.
+ *
+ * @return $this
+ */
+ public function setJsonPrettyPrint(bool $enable): self
+ {
+ if ($enable) {
+ $this->jsonEncodeOptions |= JSON_PRETTY_PRINT;
+ } else {
+ $this->jsonEncodeOptions &= ~JSON_PRETTY_PRINT;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Setting a base path will hide the base path from exception and stack trace file names to shorten them
+ * @return $this
+ */
+ public function setBasePath(string $path = ''): self
+ {
+ if ($path !== '') {
+ $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+ }
+
+ $this->basePath = $path;
+
+ return $this;
+ }
+
+ /**
+ * Provided as extension point
+ *
+ * Because normalize is called with sub-values of context data etc, normalizeRecord can be
+ * extended when data needs to be appended on the record array but not to other normalized data.
+ *
+ * @return array
+ */
+ protected function normalizeRecord(LogRecord $record): array
+ {
+ /** @var array $normalized */
+ $normalized = $this->normalize($record->toArray());
+
+ return $normalized;
+ }
+
+ /**
+ * @return null|scalar|array
+ */
+ protected function normalize(mixed $data, int $depth = 0): mixed
+ {
+ if ($depth > $this->maxNormalizeDepth) {
+ return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
+ }
+
+ if (null === $data || \is_scalar($data)) {
+ if (\is_float($data)) {
+ if (is_infinite($data)) {
+ return ($data > 0 ? '' : '-') . 'INF';
+ }
+ if (is_nan($data)) {
+ return 'NaN';
+ }
+ }
+
+ return $data;
+ }
+
+ if (\is_array($data)) {
+ $normalized = [];
+
+ $count = 1;
+ foreach ($data as $key => $value) {
+ if ($count++ > $this->maxNormalizeItemCount) {
+ $normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.\count($data).' total), aborting normalization';
+ break;
+ }
+
+ $normalized[$key] = $this->normalize($value, $depth + 1);
+ }
+
+ return $normalized;
+ }
+
+ if ($data instanceof \DateTimeInterface) {
+ return $this->formatDate($data);
+ }
+
+ if (\is_object($data)) {
+ if ($data instanceof Throwable) {
+ return $this->normalizeException($data, $depth);
+ }
+
+ if ($data instanceof \JsonSerializable) {
+ /** @var null|scalar|array $value */
+ $value = $data->jsonSerialize();
+ } elseif (\get_class($data) === '__PHP_Incomplete_Class') {
+ $accessor = new \ArrayObject($data);
+ $value = (string) $accessor['__PHP_Incomplete_Class_Name'];
+ } elseif (method_exists($data, '__toString')) {
+ try {
+ /** @var string $value */
+ $value = $data->__toString();
+ } catch (\Throwable) {
+ // if the toString method is failing, use the default behavior
+ /** @var null|scalar|array $value */
+ $value = json_decode($this->toJson($data, true), true);
+ }
+ } else {
+ // the rest is normalized by json encoding and decoding it
+ /** @var null|scalar|array $value */
+ $value = json_decode($this->toJson($data, true), true);
+ }
+
+ return [Utils::getClass($data) => $value];
+ }
+
+ if (\is_resource($data)) {
+ return sprintf('[resource(%s)]', get_resource_type($data));
+ }
+
+ return '[unknown('.\gettype($data).')]';
+ }
+
+ /**
+ * @return array>>
+ */
+ protected function normalizeException(Throwable $e, int $depth = 0)
+ {
+ if ($depth > $this->maxNormalizeDepth) {
+ return ['Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization'];
+ }
+
+ if ($e instanceof \JsonSerializable) {
+ return (array) $e->jsonSerialize();
+ }
+
+ $file = $e->getFile();
+ if ($this->basePath !== '') {
+ $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);
+ }
+
+ $data = [
+ 'class' => Utils::getClass($e),
+ 'message' => $e->getMessage(),
+ 'code' => (int) $e->getCode(),
+ 'file' => $file.':'.$e->getLine(),
+ ];
+
+ if ($e instanceof \SoapFault) {
+ if (isset($e->faultcode)) {
+ $data['faultcode'] = $e->faultcode;
+ }
+
+ if (isset($e->faultactor)) {
+ $data['faultactor'] = $e->faultactor;
+ }
+
+ if (isset($e->detail)) {
+ if (\is_string($e->detail)) {
+ $data['detail'] = $e->detail;
+ } elseif (\is_object($e->detail) || \is_array($e->detail)) {
+ $data['detail'] = $this->toJson($e->detail, true);
+ }
+ }
+ }
+
+ $trace = $e->getTrace();
+ foreach ($trace as $frame) {
+ if (isset($frame['file'], $frame['line'])) {
+ $file = $frame['file'];
+ if ($this->basePath !== '') {
+ $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);
+ }
+ $data['trace'][] = $file.':'.$frame['line'];
+ }
+ }
+
+ if (($previous = $e->getPrevious()) instanceof \Throwable) {
+ $data['previous'] = $this->normalizeException($previous, $depth + 1);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Return the JSON representation of a value
+ *
+ * @param mixed $data
+ * @throws \RuntimeException if encoding fails and errors are not ignored
+ * @return string if encoding fails and ignoreErrors is true 'null' is returned
+ */
+ protected function toJson($data, bool $ignoreErrors = false): string
+ {
+ return Utils::jsonEncode($data, $this->jsonEncodeOptions, $ignoreErrors);
+ }
+
+ protected function formatDate(\DateTimeInterface $date): string
+ {
+ // in case the date format isn't custom then we defer to the custom JsonSerializableDateTimeImmutable
+ // formatting logic, which will pick the right format based on whether useMicroseconds is on
+ if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof JsonSerializableDateTimeImmutable) {
+ return (string) $date;
+ }
+
+ return $date->format($this->dateFormat);
+ }
+
+ /**
+ * @return $this
+ */
+ public function addJsonEncodeOption(int $option): self
+ {
+ $this->jsonEncodeOptions |= $option;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function removeJsonEncodeOption(int $option): self
+ {
+ $this->jsonEncodeOptions &= ~$option;
+
+ return $this;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php
new file mode 100644
index 0000000..ec73a0e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/ScalarFormatter.php
@@ -0,0 +1,49 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\LogRecord;
+
+/**
+ * Formats data into an associative array of scalar (+ null) values.
+ * Objects and arrays will be JSON encoded.
+ *
+ * @author Andrew Lawson
+ */
+class ScalarFormatter extends NormalizerFormatter
+{
+ /**
+ * @inheritDoc
+ *
+ * @phpstan-return array $record
+ */
+ public function format(LogRecord $record): array
+ {
+ $result = [];
+ foreach ($record->toArray() as $key => $value) {
+ $result[$key] = $this->toScalar($value);
+ }
+
+ return $result;
+ }
+
+ protected function toScalar(mixed $value): string|int|float|bool|null
+ {
+ $normalized = $this->normalize($value);
+
+ if (\is_array($normalized)) {
+ return $this->toJson($normalized, true);
+ }
+
+ return $normalized;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php
new file mode 100644
index 0000000..ccaddf7
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/SyslogFormatter.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Serializes a log message according to RFC 5424
+ *
+ * @author Dalibor Karlović
+ * @author Renat Gabdullin
+ */
+class SyslogFormatter extends LineFormatter
+{
+ private const SYSLOG_FACILITY_USER = 1;
+ private const FORMAT = "<%extra.priority%>1 %datetime% %extra.hostname% %extra.app-name% %extra.procid% %channel% %extra.structured-data% %level_name%: %message% %context% %extra%\n";
+ private const NILVALUE = '-';
+
+ private string $hostname;
+ private int $procid;
+
+ public function __construct(private string $applicationName = self::NILVALUE)
+ {
+ parent::__construct(self::FORMAT, 'Y-m-d\TH:i:s.uP', true, true);
+ $this->hostname = (string) gethostname();
+ $this->procid = (int) getmypid();
+ }
+
+ public function format(LogRecord $record): string
+ {
+ $record->extra = $this->formatExtra($record);
+
+ return parent::format($record);
+ }
+
+ /**
+ * @return array
+ */
+ private function formatExtra(LogRecord $record): array
+ {
+ $extra = $record->extra;
+ $extra['app-name'] = $this->applicationName;
+ $extra['hostname'] = $this->hostname;
+ $extra['procid'] = $this->procid;
+ $extra['priority'] = self::calculatePriority($record->level);
+ $extra['structured-data'] = self::NILVALUE;
+
+ return $extra;
+ }
+
+ private static function calculatePriority(Level $level): int
+ {
+ return (self::SYSLOG_FACILITY_USER * 8) + $level->toRFC5424Level();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php
new file mode 100644
index 0000000..4acfb92
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php
@@ -0,0 +1,137 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Serializes a log message according to Wildfire's header requirements
+ *
+ * @author Eric Clemmons (@ericclemmons)
+ * @author Christophe Coevoet
+ * @author Kirill chEbba Chebunin
+ */
+class WildfireFormatter extends NormalizerFormatter
+{
+ /**
+ * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
+ */
+ public function __construct(?string $dateFormat = null)
+ {
+ parent::__construct($dateFormat);
+
+ // http headers do not like non-ISO-8559-1 characters
+ $this->removeJsonEncodeOption(JSON_UNESCAPED_UNICODE);
+ }
+
+ /**
+ * Translates Monolog log levels to Wildfire levels.
+ *
+ * @return 'LOG'|'INFO'|'WARN'|'ERROR'
+ */
+ private function toWildfireLevel(Level $level): string
+ {
+ return match ($level) {
+ Level::Debug => 'LOG',
+ Level::Info => 'INFO',
+ Level::Notice => 'INFO',
+ Level::Warning => 'WARN',
+ Level::Error => 'ERROR',
+ Level::Critical => 'ERROR',
+ Level::Alert => 'ERROR',
+ Level::Emergency => 'ERROR',
+ };
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function format(LogRecord $record): string
+ {
+ // Retrieve the line and file if set and remove them from the formatted extra
+ $file = $line = '';
+ if (isset($record->extra['file'])) {
+ $file = $record->extra['file'];
+ unset($record->extra['file']);
+ }
+ if (isset($record->extra['line'])) {
+ $line = $record->extra['line'];
+ unset($record->extra['line']);
+ }
+
+ $message = ['message' => $record->message];
+ $handleError = false;
+ if (\count($record->context) > 0) {
+ $message['context'] = $this->normalize($record->context);
+ $handleError = true;
+ }
+ if (\count($record->extra) > 0) {
+ $message['extra'] = $this->normalize($record->extra);
+ $handleError = true;
+ }
+ if (\count($message) === 1) {
+ $message = reset($message);
+ }
+
+ if (is_array($message) && isset($message['context']) && \is_array($message['context']) && isset($message['context']['table'])) {
+ $type = 'TABLE';
+ $label = $record->channel .': '. $record->message;
+ $message = $message['context']['table'];
+ } else {
+ $type = $this->toWildfireLevel($record->level);
+ $label = $record->channel;
+ }
+
+ // Create JSON object describing the appearance of the message in the console
+ $json = $this->toJson([
+ [
+ 'Type' => $type,
+ 'File' => $file,
+ 'Line' => $line,
+ 'Label' => $label,
+ ],
+ $message,
+ ], $handleError);
+
+ // The message itself is a serialization of the above JSON object + it's length
+ return sprintf(
+ '%d|%s|',
+ \strlen($json),
+ $json
+ );
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @phpstan-return never
+ */
+ public function formatBatch(array $records)
+ {
+ throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return null|scalar|array|object
+ */
+ protected function normalize(mixed $data, int $depth = 0): mixed
+ {
+ if (\is_object($data) && !$data instanceof \DateTimeInterface) {
+ return $data;
+ }
+
+ return parent::normalize($data, $depth);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php
new file mode 100644
index 0000000..61d45d5
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php
@@ -0,0 +1,104 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Logger;
+use Monolog\ResettableInterface;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Base Handler class providing basic level/bubble support
+ *
+ * @author Jordi Boggiano
+ */
+abstract class AbstractHandler extends Handler implements ResettableInterface
+{
+ protected Level $level = Level::Debug;
+ protected bool $bubble = true;
+
+ /**
+ * @param int|string|Level|LogLevel::* $level The minimum logging level at which this handler will be triggered
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $level
+ */
+ public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ $this->setLevel($level);
+ $this->bubble = $bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return $record->level->value >= $this->level->value;
+ }
+
+ /**
+ * Sets minimum logging level at which this handler will be triggered.
+ *
+ * @param Level|LogLevel::* $level Level or level name
+ * @return $this
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $level
+ */
+ public function setLevel(int|string|Level $level): self
+ {
+ $this->level = Logger::toMonologLevel($level);
+
+ return $this;
+ }
+
+ /**
+ * Gets minimum logging level at which this handler will be triggered.
+ */
+ public function getLevel(): Level
+ {
+ return $this->level;
+ }
+
+ /**
+ * Sets the bubbling behavior.
+ *
+ * @param bool $bubble true means that this handler allows bubbling.
+ * false means that bubbling is not permitted.
+ * @return $this
+ */
+ public function setBubble(bool $bubble): self
+ {
+ $this->bubble = $bubble;
+
+ return $this;
+ }
+
+ /**
+ * Gets the bubbling behavior.
+ *
+ * @return bool true means that this handler allows bubbling.
+ * false means that bubbling is not permitted.
+ */
+ public function getBubble(): bool
+ {
+ return $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function reset(): void
+ {
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php
new file mode 100644
index 0000000..de13a76
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\LogRecord;
+
+/**
+ * Base Handler class providing the Handler structure, including processors and formatters
+ *
+ * Classes extending it should (in most cases) only implement write($record)
+ *
+ * @author Jordi Boggiano
+ * @author Christophe Coevoet
+ */
+abstract class AbstractProcessingHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
+{
+ use ProcessableHandlerTrait;
+ use FormattableHandlerTrait;
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (!$this->isHandling($record)) {
+ return false;
+ }
+
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+
+ $record->formatted = $this->getFormatter()->format($record);
+
+ $this->write($record);
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * Writes the (already formatted) record down to the log of the implementing handler
+ */
+ abstract protected function write(LogRecord $record): void;
+
+ public function reset(): void
+ {
+ parent::reset();
+
+ $this->resetProcessors();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php
new file mode 100644
index 0000000..4a70317
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractSyslogHandler.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * Common syslog functionality
+ */
+abstract class AbstractSyslogHandler extends AbstractProcessingHandler
+{
+ protected int $facility;
+
+ /**
+ * List of valid log facility names.
+ * @var array
+ */
+ protected array $facilities = [
+ 'auth' => \LOG_AUTH,
+ 'authpriv' => \LOG_AUTHPRIV,
+ 'cron' => \LOG_CRON,
+ 'daemon' => \LOG_DAEMON,
+ 'kern' => \LOG_KERN,
+ 'lpr' => \LOG_LPR,
+ 'mail' => \LOG_MAIL,
+ 'news' => \LOG_NEWS,
+ 'syslog' => \LOG_SYSLOG,
+ 'user' => \LOG_USER,
+ 'uucp' => \LOG_UUCP,
+ ];
+
+ /**
+ * Translates Monolog log levels to syslog log priorities.
+ */
+ protected function toSyslogPriority(Level $level): int
+ {
+ return $level->toRFC5424Level();
+ }
+
+ /**
+ * @param string|int $facility Either one of the names of the keys in $this->facilities, or a LOG_* facility constant
+ */
+ public function __construct(string|int $facility = \LOG_USER, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+
+ if (!\defined('PHP_WINDOWS_VERSION_BUILD')) {
+ $this->facilities['local0'] = \LOG_LOCAL0;
+ $this->facilities['local1'] = \LOG_LOCAL1;
+ $this->facilities['local2'] = \LOG_LOCAL2;
+ $this->facilities['local3'] = \LOG_LOCAL3;
+ $this->facilities['local4'] = \LOG_LOCAL4;
+ $this->facilities['local5'] = \LOG_LOCAL5;
+ $this->facilities['local6'] = \LOG_LOCAL6;
+ $this->facilities['local7'] = \LOG_LOCAL7;
+ } else {
+ $this->facilities['local0'] = 128; // LOG_LOCAL0
+ $this->facilities['local1'] = 136; // LOG_LOCAL1
+ $this->facilities['local2'] = 144; // LOG_LOCAL2
+ $this->facilities['local3'] = 152; // LOG_LOCAL3
+ $this->facilities['local4'] = 160; // LOG_LOCAL4
+ $this->facilities['local5'] = 168; // LOG_LOCAL5
+ $this->facilities['local6'] = 176; // LOG_LOCAL6
+ $this->facilities['local7'] = 184; // LOG_LOCAL7
+ }
+
+ // convert textual description of facility to syslog constant
+ if (\is_string($facility) && \array_key_exists(strtolower($facility), $this->facilities)) {
+ $facility = $this->facilities[strtolower($facility)];
+ } elseif (!\in_array($facility, array_values($this->facilities), true)) {
+ throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
+ }
+
+ $this->facility = $facility;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php
new file mode 100644
index 0000000..119f339
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Gelf\Message as GelfMessage;
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\JsonFormatter;
+use PhpAmqpLib\Message\AMQPMessage;
+use PhpAmqpLib\Channel\AMQPChannel;
+use AMQPExchange;
+use Monolog\LogRecord;
+
+class AmqpHandler extends AbstractProcessingHandler
+{
+ protected AMQPExchange|AMQPChannel $exchange;
+
+ /** @var array */
+ private array $extraAttributes = [];
+
+ protected string $exchangeName;
+
+ /**
+ * @param AMQPExchange|AMQPChannel $exchange AMQPExchange (php AMQP ext) or PHP AMQP lib channel, ready for use
+ * @param string|null $exchangeName Optional exchange name, for AMQPChannel (PhpAmqpLib) only
+ */
+ public function __construct(AMQPExchange|AMQPChannel $exchange, ?string $exchangeName = null, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ if ($exchange instanceof AMQPChannel) {
+ $this->exchangeName = (string) $exchangeName;
+ } elseif ($exchangeName !== null) {
+ @trigger_error('The $exchangeName parameter can only be passed when using PhpAmqpLib, if using an AMQPExchange instance configure it beforehand', E_USER_DEPRECATED);
+ }
+ $this->exchange = $exchange;
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @return array
+ */
+ public function getExtraAttributes(): array
+ {
+ return $this->extraAttributes;
+ }
+
+ /**
+ * Configure extra attributes to pass to the AMQPExchange (if you are using the amqp extension)
+ *
+ * @param array $extraAttributes One of content_type, content_encoding,
+ * message_id, user_id, app_id, delivery_mode,
+ * priority, timestamp, expiration, type
+ * or reply_to, headers.
+ * @return $this
+ */
+ public function setExtraAttributes(array $extraAttributes): self
+ {
+ $this->extraAttributes = $extraAttributes;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $data = $record->formatted;
+ $routingKey = $this->getRoutingKey($record);
+
+ if($data instanceof GelfMessage) {
+ $data = json_encode($data->toArray());
+ }
+
+ if ($this->exchange instanceof AMQPExchange) {
+ $attributes = [
+ 'delivery_mode' => 2,
+ 'content_type' => 'application/json',
+ ];
+ if (\count($this->extraAttributes) > 0) {
+ $attributes = array_merge($attributes, $this->extraAttributes);
+ }
+ $this->exchange->publish(
+ $data,
+ $routingKey,
+ 0,
+ $attributes
+ );
+ } else {
+ $this->exchange->basic_publish(
+ $this->createAmqpMessage($data),
+ $this->exchangeName,
+ $routingKey
+ );
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ if ($this->exchange instanceof AMQPExchange) {
+ parent::handleBatch($records);
+
+ return;
+ }
+
+ foreach ($records as $record) {
+ if (!$this->isHandling($record)) {
+ continue;
+ }
+
+ $record = $this->processRecord($record);
+ $data = $this->getFormatter()->format($record);
+
+ if($data instanceof GelfMessage) {
+ $data = json_encode($data->toArray());
+ }
+
+ $this->exchange->batch_basic_publish(
+ $this->createAmqpMessage($data),
+ $this->exchangeName,
+ $this->getRoutingKey($record)
+ );
+ }
+
+ $this->exchange->publish_batch();
+ }
+
+ /**
+ * Gets the routing key for the AMQP exchange
+ */
+ protected function getRoutingKey(LogRecord $record): string
+ {
+ $routingKey = sprintf('%s.%s', $record->level->name, $record->channel);
+
+ return strtolower($routingKey);
+ }
+
+ private function createAmqpMessage(string $data): AMQPMessage
+ {
+ $attributes = [
+ 'delivery_mode' => 2,
+ 'content_type' => 'application/json',
+ ];
+ if (\count($this->extraAttributes) > 0) {
+ $attributes = array_merge($attributes, $this->extraAttributes);
+ }
+
+ return new AMQPMessage($data, $attributes);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php
new file mode 100644
index 0000000..788d7d0
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/BrowserConsoleHandler.php
@@ -0,0 +1,300 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Utils;
+use Monolog\LogRecord;
+use Monolog\Level;
+
+use function headers_list;
+use function stripos;
+
+/**
+ * Handler sending logs to browser's javascript console with no browser extension required
+ *
+ * @author Olivier Poitrey
+ */
+class BrowserConsoleHandler extends AbstractProcessingHandler
+{
+ protected static bool $initialized = false;
+
+ /** @var LogRecord[] */
+ protected static array $records = [];
+
+ protected const FORMAT_HTML = 'html';
+ protected const FORMAT_JS = 'js';
+ protected const FORMAT_UNKNOWN = 'unknown';
+
+ /**
+ * @inheritDoc
+ *
+ * Formatted output may contain some formatting markers to be transferred to `console.log` using the %c format.
+ *
+ * Example of formatted string:
+ *
+ * You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter('[[%channel%]]{macro: autolabel} [[%level_name%]]{font-weight: bold} %message%');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ // Accumulate records
+ static::$records[] = $record;
+
+ // Register shutdown handler if not already done
+ if (!static::$initialized) {
+ static::$initialized = true;
+ $this->registerShutdownFunction();
+ }
+ }
+
+ /**
+ * Convert records to javascript console commands and send it to the browser.
+ * This method is automatically called on PHP shutdown if output is HTML or Javascript.
+ */
+ public static function send(): void
+ {
+ $format = static::getResponseFormat();
+ if ($format === self::FORMAT_UNKNOWN) {
+ return;
+ }
+
+ if (\count(static::$records) > 0) {
+ if ($format === self::FORMAT_HTML) {
+ static::writeOutput('');
+ } else { // js format
+ static::writeOutput(self::generateScript());
+ }
+ static::resetStatic();
+ }
+ }
+
+ public function close(): void
+ {
+ self::resetStatic();
+ }
+
+ public function reset(): void
+ {
+ parent::reset();
+
+ self::resetStatic();
+ }
+
+ /**
+ * Forget all logged records
+ */
+ public static function resetStatic(): void
+ {
+ static::$records = [];
+ }
+
+ /**
+ * Wrapper for register_shutdown_function to allow overriding
+ */
+ protected function registerShutdownFunction(): void
+ {
+ if (PHP_SAPI !== 'cli') {
+ register_shutdown_function(['Monolog\Handler\BrowserConsoleHandler', 'send']);
+ }
+ }
+
+ /**
+ * Wrapper for echo to allow overriding
+ */
+ protected static function writeOutput(string $str): void
+ {
+ echo $str;
+ }
+
+ /**
+ * Checks the format of the response
+ *
+ * If Content-Type is set to application/javascript or text/javascript -> js
+ * If Content-Type is set to text/html, or is unset -> html
+ * If Content-Type is anything else -> unknown
+ *
+ * @return string One of 'js', 'html' or 'unknown'
+ * @phpstan-return self::FORMAT_*
+ */
+ protected static function getResponseFormat(): string
+ {
+ // Check content type
+ foreach (headers_list() as $header) {
+ if (stripos($header, 'content-type:') === 0) {
+ return static::getResponseFormatFromContentType($header);
+ }
+ }
+
+ return self::FORMAT_HTML;
+ }
+
+ /**
+ * @return string One of 'js', 'html' or 'unknown'
+ * @phpstan-return self::FORMAT_*
+ */
+ protected static function getResponseFormatFromContentType(string $contentType): string
+ {
+ // This handler only works with HTML and javascript outputs
+ // text/javascript is obsolete in favour of application/javascript, but still used
+ if (stripos($contentType, 'application/javascript') !== false || stripos($contentType, 'text/javascript') !== false) {
+ return self::FORMAT_JS;
+ }
+
+ if (stripos($contentType, 'text/html') !== false) {
+ return self::FORMAT_HTML;
+ }
+
+ return self::FORMAT_UNKNOWN;
+ }
+
+ private static function generateScript(): string
+ {
+ $script = [];
+ foreach (static::$records as $record) {
+ $context = self::dump('Context', $record->context);
+ $extra = self::dump('Extra', $record->extra);
+
+ if (\count($context) === 0 && \count($extra) === 0) {
+ $script[] = self::call_array(self::getConsoleMethodForLevel($record->level), self::handleStyles($record->formatted));
+ } else {
+ $script = array_merge(
+ $script,
+ [self::call_array('groupCollapsed', self::handleStyles($record->formatted))],
+ $context,
+ $extra,
+ [self::call('groupEnd')]
+ );
+ }
+ }
+
+ return "(function (c) {if (c && c.groupCollapsed) {\n" . implode("\n", $script) . "\n}})(console);";
+ }
+
+ private static function getConsoleMethodForLevel(Level $level): string
+ {
+ return match ($level) {
+ Level::Debug => 'debug',
+ Level::Info, Level::Notice => 'info',
+ Level::Warning => 'warn',
+ Level::Error, Level::Critical, Level::Alert, Level::Emergency => 'error',
+ };
+ }
+
+ /**
+ * @return string[]
+ */
+ private static function handleStyles(string $formatted): array
+ {
+ $args = [];
+ $format = '%c' . $formatted;
+ preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
+
+ foreach (array_reverse($matches) as $match) {
+ $args[] = '"font-weight: normal"';
+ $args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
+
+ $pos = $match[0][1];
+ $format = Utils::substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . Utils::substr($format, $pos + \strlen($match[0][0]));
+ }
+
+ $args[] = self::quote('font-weight: normal');
+ $args[] = self::quote($format);
+
+ return array_reverse($args);
+ }
+
+ private static function handleCustomStyles(string $style, string $string): string
+ {
+ static $colors = ['blue', 'green', 'red', 'magenta', 'orange', 'black', 'grey'];
+ static $labels = [];
+
+ $style = preg_replace_callback('/macro\s*:(.*?)(?:;|$)/', function (array $m) use ($string, &$colors, &$labels) {
+ if (trim($m[1]) === 'autolabel') {
+ // Format the string as a label with consistent auto assigned background color
+ if (!isset($labels[$string])) {
+ $labels[$string] = $colors[\count($labels) % \count($colors)];
+ }
+ $color = $labels[$string];
+
+ return "background-color: $color; color: white; border-radius: 3px; padding: 0 2px 0 2px";
+ }
+
+ return $m[1];
+ }, $style);
+
+ if (null === $style) {
+ $pcreErrorCode = preg_last_error();
+
+ throw new \RuntimeException('Failed to run preg_replace_callback: ' . $pcreErrorCode . ' / ' . preg_last_error_msg());
+ }
+
+ return $style;
+ }
+
+ /**
+ * @param mixed[] $dict
+ * @return mixed[]
+ */
+ private static function dump(string $title, array $dict): array
+ {
+ $script = [];
+ $dict = array_filter($dict, fn ($value) => $value !== null);
+ if (\count($dict) === 0) {
+ return $script;
+ }
+ $script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
+ foreach ($dict as $key => $value) {
+ $value = json_encode($value);
+ if (false === $value) {
+ $value = self::quote('');
+ }
+ $script[] = self::call('log', self::quote('%s: %o'), self::quote((string) $key), $value);
+ }
+
+ return $script;
+ }
+
+ private static function quote(string $arg): string
+ {
+ return '"' . addcslashes($arg, "\"\n\\") . '"';
+ }
+
+ /**
+ * @param mixed $args
+ */
+ private static function call(...$args): string
+ {
+ $method = array_shift($args);
+ if (!\is_string($method)) {
+ throw new \UnexpectedValueException('Expected the first arg to be a string, got: '.var_export($method, true));
+ }
+
+ return self::call_array($method, $args);
+ }
+
+ /**
+ * @param mixed[] $args
+ */
+ private static function call_array(string $method, array $args): string
+ {
+ return 'c.' . $method . '(' . implode(', ', $args) . ');';
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php
new file mode 100644
index 0000000..c799b6b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php
@@ -0,0 +1,170 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\ResettableInterface;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Buffers all records until closing the handler and then pass them as batch.
+ *
+ * This is useful for a MailHandler to send only one mail per request instead of
+ * sending one per log message.
+ *
+ * @author Christophe Coevoet
+ */
+class BufferHandler extends AbstractHandler implements ProcessableHandlerInterface, FormattableHandlerInterface
+{
+ use ProcessableHandlerTrait;
+
+ protected HandlerInterface $handler;
+
+ protected int $bufferSize = 0;
+
+ protected int $bufferLimit;
+
+ protected bool $flushOnOverflow;
+
+ /** @var LogRecord[] */
+ protected array $buffer = [];
+
+ protected bool $initialized = false;
+
+ /**
+ * @param HandlerInterface $handler Handler.
+ * @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
+ * @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
+ */
+ public function __construct(HandlerInterface $handler, int $bufferLimit = 0, int|string|Level $level = Level::Debug, bool $bubble = true, bool $flushOnOverflow = false)
+ {
+ parent::__construct($level, $bubble);
+ $this->handler = $handler;
+ $this->bufferLimit = $bufferLimit;
+ $this->flushOnOverflow = $flushOnOverflow;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if ($record->level->isLowerThan($this->level)) {
+ return false;
+ }
+
+ if (!$this->initialized) {
+ // __destructor() doesn't get called on Fatal errors
+ register_shutdown_function([$this, 'close']);
+ $this->initialized = true;
+ }
+
+ if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
+ if ($this->flushOnOverflow) {
+ $this->flush();
+ } else {
+ array_shift($this->buffer);
+ $this->bufferSize--;
+ }
+ }
+
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+
+ $this->buffer[] = $record;
+ $this->bufferSize++;
+
+ return false === $this->bubble;
+ }
+
+ public function flush(): void
+ {
+ if ($this->bufferSize === 0) {
+ return;
+ }
+
+ $this->handler->handleBatch($this->buffer);
+ $this->clear();
+ }
+
+ public function __destruct()
+ {
+ // suppress the parent behavior since we already have register_shutdown_function()
+ // to call close(), and the reference contained there will prevent this from being
+ // GC'd until the end of the request
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function close(): void
+ {
+ $this->flush();
+
+ $this->handler->close();
+ }
+
+ /**
+ * Clears the buffer without flushing any messages down to the wrapped handler.
+ */
+ public function clear(): void
+ {
+ $this->bufferSize = 0;
+ $this->buffer = [];
+ }
+
+ public function reset(): void
+ {
+ $this->flush();
+
+ parent::reset();
+
+ $this->resetProcessors();
+
+ if ($this->handler instanceof ResettableInterface) {
+ $this->handler->reset();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ $this->handler->setFormatter($formatter);
+
+ return $this;
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ return $this->handler->getFormatter();
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.');
+ }
+
+ public function setHandler(HandlerInterface $handler): void
+ {
+ $this->handler = $handler;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php
new file mode 100644
index 0000000..6598e82
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php
@@ -0,0 +1,186 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\ChromePHPFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\LogRecord;
+use Monolog\JsonSerializableDateTimeImmutable;
+
+/**
+ * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
+ *
+ * This also works out of the box with Firefox 43+
+ *
+ * @author Christophe Coevoet
+ */
+class ChromePHPHandler extends AbstractProcessingHandler
+{
+ use WebRequestRecognizerTrait;
+
+ /**
+ * Version of the extension
+ */
+ protected const VERSION = '4.0';
+
+ /**
+ * Header name
+ */
+ protected const HEADER_NAME = 'X-ChromeLogger-Data';
+
+ /**
+ * Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
+ */
+ protected const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|HeadlessChrome|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';
+
+ protected static bool $initialized = false;
+
+ /**
+ * Tracks whether we sent too much data
+ *
+ * Chrome limits the headers to 4KB, so when we sent 3KB we stop sending
+ */
+ protected static bool $overflowed = false;
+
+ /** @var mixed[] */
+ protected static array $json = [
+ 'version' => self::VERSION,
+ 'columns' => ['label', 'log', 'backtrace', 'type'],
+ 'rows' => [],
+ ];
+
+ protected static bool $sendHeaders = true;
+
+ public function __construct(int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ if (!$this->isWebRequest()) {
+ return;
+ }
+
+ $messages = [];
+
+ foreach ($records as $record) {
+ if ($record->level < $this->level) {
+ continue;
+ }
+
+ $message = $this->processRecord($record);
+ $messages[] = $message;
+ }
+
+ if (\count($messages) > 0) {
+ $messages = $this->getFormatter()->formatBatch($messages);
+ self::$json['rows'] = array_merge(self::$json['rows'], $messages);
+ $this->send();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new ChromePHPFormatter();
+ }
+
+ /**
+ * Creates & sends header for a record
+ *
+ * @see sendHeader()
+ * @see send()
+ */
+ protected function write(LogRecord $record): void
+ {
+ if (!$this->isWebRequest()) {
+ return;
+ }
+
+ self::$json['rows'][] = $record->formatted;
+
+ $this->send();
+ }
+
+ /**
+ * Sends the log header
+ *
+ * @see sendHeader()
+ */
+ protected function send(): void
+ {
+ if (self::$overflowed || !self::$sendHeaders) {
+ return;
+ }
+
+ if (!self::$initialized) {
+ self::$initialized = true;
+
+ self::$sendHeaders = $this->headersAccepted();
+ if (!self::$sendHeaders) {
+ return;
+ }
+
+ self::$json['request_uri'] = $_SERVER['REQUEST_URI'] ?? '';
+ }
+
+ $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
+ $data = base64_encode($json);
+ if (\strlen($data) > 3 * 1024) {
+ self::$overflowed = true;
+
+ $record = new LogRecord(
+ message: 'Incomplete logs, chrome header size limit reached',
+ level: Level::Warning,
+ channel: 'monolog',
+ datetime: new JsonSerializableDateTimeImmutable(true),
+ );
+ self::$json['rows'][\count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
+ $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true);
+ $data = base64_encode($json);
+ }
+
+ if (trim($data) !== '') {
+ $this->sendHeader(static::HEADER_NAME, $data);
+ }
+ }
+
+ /**
+ * Send header string to the client
+ */
+ protected function sendHeader(string $header, string $content): void
+ {
+ if (!headers_sent() && self::$sendHeaders) {
+ header(sprintf('%s: %s', $header, $content));
+ }
+ }
+
+ /**
+ * Verifies if the headers are accepted by the current user agent
+ */
+ protected function headersAccepted(): bool
+ {
+ if (!isset($_SERVER['HTTP_USER_AGENT'])) {
+ return false;
+ }
+
+ return preg_match(static::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']) === 1;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php
new file mode 100644
index 0000000..8d9c10e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\JsonFormatter;
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * CouchDB handler
+ *
+ * @author Markus Bachmann
+ * @phpstan-type Options array{
+ * host: string,
+ * port: int,
+ * dbname: string,
+ * username: string|null,
+ * password: string|null
+ * }
+ * @phpstan-type InputOptions array{
+ * host?: string,
+ * port?: int,
+ * dbname?: string,
+ * username?: string|null,
+ * password?: string|null
+ * }
+ */
+class CouchDBHandler extends AbstractProcessingHandler
+{
+ /**
+ * @var mixed[]
+ * @phpstan-var Options
+ */
+ private array $options;
+
+ /**
+ * @param mixed[] $options
+ *
+ * @phpstan-param InputOptions $options
+ */
+ public function __construct(array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ $this->options = array_merge([
+ 'host' => 'localhost',
+ 'port' => 5984,
+ 'dbname' => 'logger',
+ 'username' => null,
+ 'password' => null,
+ ], $options);
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $basicAuth = null;
+ if (null !== $this->options['username'] && null !== $this->options['password']) {
+ $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
+ }
+
+ $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'POST',
+ 'content' => $record->formatted,
+ 'ignore_errors' => true,
+ 'max_redirects' => 0,
+ 'header' => 'Content-type: application/json',
+ ],
+ ]);
+
+ if (false === @file_get_contents($url, false, $context)) {
+ throw new \RuntimeException(sprintf('Could not connect to %s', $url));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php
new file mode 100644
index 0000000..f9ebede
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php
@@ -0,0 +1,167 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Logs to Cube.
+ *
+ * @link https://github.com/square/cube/wiki
+ * @author Wan Chen
+ * @deprecated Since 2.8.0 and 3.2.0, Cube appears abandoned and thus we will drop this handler in Monolog 4
+ */
+class CubeHandler extends AbstractProcessingHandler
+{
+ private ?\Socket $udpConnection = null;
+ private ?\CurlHandle $httpConnection = null;
+ private string $scheme;
+ private string $host;
+ private int $port;
+ /** @var string[] */
+ private array $acceptedSchemes = ['http', 'udp'];
+
+ /**
+ * Create a Cube handler
+ *
+ * @throws \UnexpectedValueException when given url is not a valid url.
+ * A valid url must consist of three parts : protocol://host:port
+ * Only valid protocols used by Cube are http and udp
+ */
+ public function __construct(string $url, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ $urlInfo = parse_url($url);
+
+ if ($urlInfo === false || !isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
+ throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
+ }
+
+ if (!\in_array($urlInfo['scheme'], $this->acceptedSchemes, true)) {
+ throw new \UnexpectedValueException(
+ 'Invalid protocol (' . $urlInfo['scheme'] . ').'
+ . ' Valid options are ' . implode(', ', $this->acceptedSchemes)
+ );
+ }
+
+ $this->scheme = $urlInfo['scheme'];
+ $this->host = $urlInfo['host'];
+ $this->port = $urlInfo['port'];
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * Establish a connection to an UDP socket
+ *
+ * @throws \LogicException when unable to connect to the socket
+ * @throws MissingExtensionException when there is no socket extension
+ */
+ protected function connectUdp(): void
+ {
+ if (!\extension_loaded('sockets')) {
+ throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
+ }
+
+ $udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
+ if (false === $udpConnection) {
+ throw new \LogicException('Unable to create a socket');
+ }
+
+ $this->udpConnection = $udpConnection;
+ if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
+ throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
+ }
+ }
+
+ /**
+ * Establish a connection to an http server
+ *
+ * @throws \LogicException when unable to connect to the socket
+ * @throws MissingExtensionException when no curl extension
+ */
+ protected function connectHttp(): void
+ {
+ if (!\extension_loaded('curl')) {
+ throw new MissingExtensionException('The curl extension is required to use http URLs with the CubeHandler');
+ }
+
+ $httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
+ if (false === $httpConnection) {
+ throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
+ }
+
+ $this->httpConnection = $httpConnection;
+ curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
+ curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $date = $record->datetime;
+
+ $data = ['time' => $date->format('Y-m-d\TH:i:s.uO')];
+ $context = $record->context;
+
+ if (isset($context['type'])) {
+ $data['type'] = $context['type'];
+ unset($context['type']);
+ } else {
+ $data['type'] = $record->channel;
+ }
+
+ $data['data'] = $context;
+ $data['data']['level'] = $record->level;
+
+ if ($this->scheme === 'http') {
+ $this->writeHttp(Utils::jsonEncode($data));
+ } else {
+ $this->writeUdp(Utils::jsonEncode($data));
+ }
+ }
+
+ private function writeUdp(string $data): void
+ {
+ if (null === $this->udpConnection) {
+ $this->connectUdp();
+ }
+
+ if (null === $this->udpConnection) {
+ throw new \LogicException('No UDP socket could be opened');
+ }
+
+ socket_send($this->udpConnection, $data, \strlen($data), 0);
+ }
+
+ private function writeHttp(string $data): void
+ {
+ if (null === $this->httpConnection) {
+ $this->connectHttp();
+ }
+
+ if (null === $this->httpConnection) {
+ throw new \LogicException('No connection could be established');
+ }
+
+ curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
+ curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, [
+ 'Content-Type: application/json',
+ 'Content-Length: ' . \strlen('['.$data.']'),
+ ]);
+
+ Curl\Util::execute($this->httpConnection, 5);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php
new file mode 100644
index 0000000..2ef5887
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/Curl/Util.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler\Curl;
+
+use CurlHandle;
+
+/**
+ * This class is marked as internal and it is not under the BC promise of the package.
+ *
+ * @internal
+ */
+final class Util
+{
+ /** @var array */
+ private static array $retriableErrorCodes = [
+ CURLE_COULDNT_RESOLVE_HOST,
+ CURLE_COULDNT_CONNECT,
+ CURLE_HTTP_NOT_FOUND,
+ CURLE_READ_ERROR,
+ CURLE_OPERATION_TIMEOUTED,
+ CURLE_HTTP_POST_ERROR,
+ CURLE_SSL_CONNECT_ERROR,
+ ];
+
+ /**
+ * Executes a CURL request with optional retries and exception on failure
+ *
+ * @param CurlHandle $ch curl handler
+ * @return bool|string @see curl_exec
+ */
+ public static function execute(CurlHandle $ch, int $retries = 5): bool|string
+ {
+ while ($retries > 0) {
+ $retries--;
+ $curlResponse = curl_exec($ch);
+ if ($curlResponse === false) {
+ $curlErrno = curl_errno($ch);
+
+ if (false === \in_array($curlErrno, self::$retriableErrorCodes, true) || $retries === 0) {
+ $curlError = curl_error($ch);
+
+ throw new \RuntimeException(sprintf('Curl error (code %d): %s', $curlErrno, $curlError));
+ }
+ continue;
+ }
+
+ return $curlResponse;
+ }
+ return false;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php
new file mode 100644
index 0000000..2b1d0b8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/DeduplicationHandler.php
@@ -0,0 +1,176 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Logger;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Simple handler wrapper that deduplicates log records across multiple requests
+ *
+ * It also includes the BufferHandler functionality and will buffer
+ * all messages until the end of the request or flush() is called.
+ *
+ * This works by storing all log records' messages above $deduplicationLevel
+ * to the file specified by $deduplicationStore. When further logs come in at the end of the
+ * request (or when flush() is called), all those above $deduplicationLevel are checked
+ * against the existing stored logs. If they match and the timestamps in the stored log is
+ * not older than $time seconds, the new log record is discarded. If no log record is new, the
+ * whole data set is discarded.
+ *
+ * This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
+ * that send messages to people, to avoid spamming with the same message over and over in case of
+ * a major component failure like a database server being down which makes all requests fail in the
+ * same way.
+ *
+ * @author Jordi Boggiano
+ */
+class DeduplicationHandler extends BufferHandler
+{
+ protected string $deduplicationStore;
+
+ protected Level $deduplicationLevel;
+
+ protected int $time;
+ protected bool $gc = false;
+
+ /**
+ * @param HandlerInterface $handler Handler.
+ * @param string|null $deduplicationStore The file/path where the deduplication log should be kept
+ * @param int|string|Level|LogLevel::* $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
+ * @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $deduplicationLevel
+ */
+ public function __construct(HandlerInterface $handler, ?string $deduplicationStore = null, int|string|Level $deduplicationLevel = Level::Error, int $time = 60, bool $bubble = true)
+ {
+ parent::__construct($handler, 0, Level::Debug, $bubble, false);
+
+ $this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
+ $this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
+ $this->time = $time;
+ }
+
+ public function flush(): void
+ {
+ if ($this->bufferSize === 0) {
+ return;
+ }
+
+ $store = null;
+
+ if (file_exists($this->deduplicationStore)) {
+ $store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ }
+
+ $passthru = null;
+
+ foreach ($this->buffer as $record) {
+ if ($record->level->value >= $this->deduplicationLevel->value) {
+ $passthru = $passthru === true || !\is_array($store) || !$this->isDuplicate($store, $record);
+ if ($passthru) {
+ $line = $this->buildDeduplicationStoreEntry($record);
+ file_put_contents($this->deduplicationStore, $line . "\n", FILE_APPEND);
+ if (!\is_array($store)) {
+ $store = [];
+ }
+ $store[] = $line;
+ }
+ }
+ }
+
+ // default of null is valid as well as if no record matches duplicationLevel we just pass through
+ if ($passthru === true || $passthru === null) {
+ $this->handler->handleBatch($this->buffer);
+ }
+
+ $this->clear();
+
+ if ($this->gc) {
+ $this->collectLogs();
+ }
+ }
+
+ /**
+ * If there is a store entry older than e.g. a day, this method should set `$this->gc` to `true` to trigger garbage collection.
+ * @param string[] $store The deduplication store
+ */
+ protected function isDuplicate(array $store, LogRecord $record): bool
+ {
+ $timestampValidity = $record->datetime->getTimestamp() - $this->time;
+ $expectedMessage = preg_replace('{[\r\n].*}', '', $record->message);
+ $yesterday = time() - 86400;
+
+ for ($i = \count($store) - 1; $i >= 0; $i--) {
+ list($timestamp, $level, $message) = explode(':', $store[$i], 3);
+
+ if ($level === $record->level->getName() && $message === $expectedMessage && $timestamp > $timestampValidity) {
+ return true;
+ }
+
+ if ($timestamp < $yesterday) {
+ $this->gc = true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return string The given record serialized as a single line of text
+ */
+ protected function buildDeduplicationStoreEntry(LogRecord $record): string
+ {
+ return $record->datetime->getTimestamp() . ':' . $record->level->getName() . ':' . preg_replace('{[\r\n].*}', '', $record->message);
+ }
+
+ private function collectLogs(): void
+ {
+ if (!file_exists($this->deduplicationStore)) {
+ return;
+ }
+
+ $handle = fopen($this->deduplicationStore, 'rw+');
+
+ if (false === $handle) {
+ throw new \RuntimeException('Failed to open file for reading and writing: ' . $this->deduplicationStore);
+ }
+
+ if (false === flock($handle, LOCK_EX)) {
+ return;
+ }
+ $validLogs = [];
+
+ $timestampValidity = time() - $this->time;
+
+ while (!feof($handle)) {
+ $log = fgets($handle);
+ if (\is_string($log) && '' !== $log && substr($log, 0, 10) >= $timestampValidity) {
+ $validLogs[] = $log;
+ }
+ }
+
+ ftruncate($handle, 0);
+ rewind($handle);
+ foreach ($validLogs as $log) {
+ fwrite($handle, $log);
+ }
+
+ flock($handle, LOCK_UN);
+ fclose($handle);
+
+ $this->gc = false;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php
new file mode 100644
index 0000000..eab9f10
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php
@@ -0,0 +1,47 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\NormalizerFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Doctrine\CouchDB\CouchDBClient;
+use Monolog\LogRecord;
+
+/**
+ * CouchDB handler for Doctrine CouchDB ODM
+ *
+ * @author Markus Bachmann
+ */
+class DoctrineCouchDBHandler extends AbstractProcessingHandler
+{
+ private CouchDBClient $client;
+
+ public function __construct(CouchDBClient $client, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ $this->client = $client;
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->client->postDocument($record->formatted);
+ }
+
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new NormalizerFormatter;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php
new file mode 100644
index 0000000..f1c5a95
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/DynamoDbHandler.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Aws\Sdk;
+use Aws\DynamoDb\DynamoDbClient;
+use Monolog\Formatter\FormatterInterface;
+use Aws\DynamoDb\Marshaler;
+use Monolog\Formatter\ScalarFormatter;
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Amazon DynamoDB handler (http://aws.amazon.com/dynamodb/)
+ *
+ * @link https://github.com/aws/aws-sdk-php/
+ * @author Andrew Lawson
+ */
+class DynamoDbHandler extends AbstractProcessingHandler
+{
+ public const DATE_FORMAT = 'Y-m-d\TH:i:s.uO';
+
+ protected DynamoDbClient $client;
+
+ protected string $table;
+
+ protected Marshaler $marshaler;
+
+ public function __construct(DynamoDbClient $client, string $table, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ $this->marshaler = new Marshaler;
+
+ $this->client = $client;
+ $this->table = $table;
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $filtered = $this->filterEmptyFields($record->formatted);
+ $formatted = $this->marshaler->marshalItem($filtered);
+
+ $this->client->putItem([
+ 'TableName' => $this->table,
+ 'Item' => $formatted,
+ ]);
+ }
+
+ /**
+ * @param mixed[] $record
+ * @return mixed[]
+ */
+ protected function filterEmptyFields(array $record): array
+ {
+ return array_filter($record, function ($value) {
+ return [] !== $value;
+ });
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new ScalarFormatter(self::DATE_FORMAT);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php
new file mode 100644
index 0000000..4a184b3
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticaHandler.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Elastic\Transport\Exception\TransportException;
+use Elastica\Document;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\ElasticaFormatter;
+use Monolog\Level;
+use Elastica\Client;
+use Elastica\Exception\ExceptionInterface;
+use Monolog\LogRecord;
+
+/**
+ * Elastic Search handler
+ *
+ * Usage example:
+ *
+ * $client = new \Elastica\Client();
+ * $options = array(
+ * 'index' => 'elastic_index_name',
+ * 'type' => 'elastic_doc_type', Types have been removed in Elastica 7
+ * );
+ * $handler = new ElasticaHandler($client, $options);
+ * $log = new Logger('application');
+ * $log->pushHandler($handler);
+ *
+ * @author Jelle Vink
+ * @phpstan-type Options array{
+ * index: string,
+ * type: string,
+ * ignore_error: bool
+ * }
+ * @phpstan-type InputOptions array{
+ * index?: string,
+ * type?: string,
+ * ignore_error?: bool
+ * }
+ */
+class ElasticaHandler extends AbstractProcessingHandler
+{
+ protected Client $client;
+
+ /**
+ * @var mixed[] Handler config options
+ * @phpstan-var Options
+ */
+ protected array $options;
+
+ /**
+ * @param Client $client Elastica Client object
+ * @param mixed[] $options Handler configuration
+ *
+ * @phpstan-param InputOptions $options
+ */
+ public function __construct(Client $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+ $this->client = $client;
+ $this->options = array_merge(
+ [
+ 'index' => 'monolog', // Elastic index name
+ 'type' => 'record', // Elastic document type
+ 'ignore_error' => false, // Suppress Elastica exceptions
+ ],
+ $options
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->bulkSend([$record->formatted]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if ($formatter instanceof ElasticaFormatter) {
+ return parent::setFormatter($formatter);
+ }
+
+ throw new \InvalidArgumentException('ElasticaHandler is only compatible with ElasticaFormatter');
+ }
+
+ /**
+ * @return mixed[]
+ *
+ * @phpstan-return Options
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new ElasticaFormatter($this->options['index'], $this->options['type']);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ $documents = $this->getFormatter()->formatBatch($records);
+ $this->bulkSend($documents);
+ }
+
+ /**
+ * Use Elasticsearch bulk API to send list of documents
+ *
+ * @param Document[] $documents
+ *
+ * @throws \RuntimeException
+ */
+ protected function bulkSend(array $documents): void
+ {
+ try {
+ $this->client->addDocuments($documents);
+ } catch (ExceptionInterface | TransportException $e) {
+ if (!$this->options['ignore_error']) {
+ throw new \RuntimeException("Error sending messages to Elasticsearch", 0, $e);
+ }
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php
new file mode 100644
index 0000000..6fd705d
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ElasticsearchHandler.php
@@ -0,0 +1,238 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Elastic\Elasticsearch\Response\Elasticsearch;
+use Throwable;
+use RuntimeException;
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\ElasticsearchFormatter;
+use InvalidArgumentException;
+use Elasticsearch\Common\Exceptions\RuntimeException as ElasticsearchRuntimeException;
+use Elasticsearch\Client;
+use Monolog\LogRecord;
+use Elastic\Elasticsearch\Exception\InvalidArgumentException as ElasticInvalidArgumentException;
+use Elastic\Elasticsearch\Client as Client8;
+
+/**
+ * Elasticsearch handler
+ *
+ * @link https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html
+ *
+ * Simple usage example:
+ *
+ * $client = \Elasticsearch\ClientBuilder::create()
+ * ->setHosts($hosts)
+ * ->build();
+ *
+ * $options = array(
+ * 'index' => 'elastic_index_name',
+ * 'type' => 'elastic_doc_type',
+ * );
+ * $handler = new ElasticsearchHandler($client, $options);
+ * $log = new Logger('application');
+ * $log->pushHandler($handler);
+ *
+ * @author Avtandil Kikabidze
+ * @phpstan-type Options array{
+ * index: string,
+ * type: string,
+ * ignore_error: bool,
+ * op_type: 'index'|'create'
+ * }
+ * @phpstan-type InputOptions array{
+ * index?: string,
+ * type?: string,
+ * ignore_error?: bool,
+ * op_type?: 'index'|'create'
+ * }
+ */
+class ElasticsearchHandler extends AbstractProcessingHandler
+{
+ protected Client|Client8 $client;
+
+ /**
+ * @var mixed[] Handler config options
+ * @phpstan-var Options
+ */
+ protected array $options;
+
+ /**
+ * @var bool
+ */
+ private $needsType;
+
+ /**
+ * @param Client|Client8 $client Elasticsearch Client object
+ * @param mixed[] $options Handler configuration
+ *
+ * @phpstan-param InputOptions $options
+ */
+ public function __construct(Client|Client8 $client, array $options = [], int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+ $this->client = $client;
+ $this->options = array_merge(
+ [
+ 'index' => 'monolog', // Elastic index name
+ 'type' => '_doc', // Elastic document type
+ 'ignore_error' => false, // Suppress Elasticsearch exceptions
+ 'op_type' => 'index', // Elastic op_type (index or create) (https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#docs-index-api-op_type)
+ ],
+ $options
+ );
+
+ if ($client instanceof Client8 || $client::VERSION[0] === '7') {
+ $this->needsType = false;
+ // force the type to _doc for ES8/ES7
+ $this->options['type'] = '_doc';
+ } else {
+ $this->needsType = true;
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->bulkSend([$record->formatted]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if ($formatter instanceof ElasticsearchFormatter) {
+ return parent::setFormatter($formatter);
+ }
+
+ throw new InvalidArgumentException('ElasticsearchHandler is only compatible with ElasticsearchFormatter');
+ }
+
+ /**
+ * Getter options
+ *
+ * @return mixed[]
+ *
+ * @phpstan-return Options
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new ElasticsearchFormatter($this->options['index'], $this->options['type']);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ $documents = $this->getFormatter()->formatBatch($records);
+ $this->bulkSend($documents);
+ }
+
+ /**
+ * Use Elasticsearch bulk API to send list of documents
+ *
+ * @param array> $records Records + _index/_type keys
+ * @throws \RuntimeException
+ */
+ protected function bulkSend(array $records): void
+ {
+ try {
+ $params = [
+ 'body' => [],
+ ];
+
+ foreach ($records as $record) {
+ $params['body'][] = [
+ $this->options['op_type'] => $this->needsType ? [
+ '_index' => $record['_index'],
+ '_type' => $record['_type'],
+ ] : [
+ '_index' => $record['_index'],
+ ],
+ ];
+ unset($record['_index'], $record['_type']);
+
+ $params['body'][] = $record;
+ }
+
+ /** @var Elasticsearch */
+ $responses = $this->client->bulk($params);
+
+ if ($responses['errors'] === true) {
+ throw $this->createExceptionFromResponses($responses);
+ }
+ } catch (Throwable $e) {
+ if (! $this->options['ignore_error']) {
+ throw new RuntimeException('Error sending messages to Elasticsearch', 0, $e);
+ }
+ }
+ }
+
+ /**
+ * Creates elasticsearch exception from responses array
+ *
+ * Only the first error is converted into an exception.
+ *
+ * @param mixed[]|Elasticsearch $responses returned by $this->client->bulk()
+ */
+ protected function createExceptionFromResponses($responses): Throwable
+ {
+ foreach ($responses['items'] ?? [] as $item) {
+ if (isset($item['index']['error'])) {
+ return $this->createExceptionFromError($item['index']['error']);
+ }
+ }
+
+ if (class_exists(ElasticInvalidArgumentException::class)) {
+ return new ElasticInvalidArgumentException('Elasticsearch failed to index one or more records.');
+ }
+
+ if (class_exists(ElasticsearchRuntimeException::class)) {
+ return new ElasticsearchRuntimeException('Elasticsearch failed to index one or more records.');
+ }
+
+ throw new \LogicException('Unsupported elastic search client version');
+ }
+
+ /**
+ * Creates elasticsearch exception from error array
+ *
+ * @param mixed[] $error
+ */
+ protected function createExceptionFromError(array $error): Throwable
+ {
+ $previous = isset($error['caused_by']) ? $this->createExceptionFromError($error['caused_by']) : null;
+
+ if (class_exists(ElasticInvalidArgumentException::class)) {
+ return new ElasticInvalidArgumentException($error['type'] . ': ' . $error['reason'], 0, $previous);
+ }
+
+ if (class_exists(ElasticsearchRuntimeException::class)) {
+ return new ElasticsearchRuntimeException($error['type'].': '.$error['reason'], 0, $previous);
+ }
+
+ throw new \LogicException('Unsupported elastic search client version');
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php
new file mode 100644
index 0000000..cd28ff6
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * Stores to PHP error_log() handler.
+ *
+ * @author Elan Ruusamäe
+ */
+class ErrorLogHandler extends AbstractProcessingHandler
+{
+ public const OPERATING_SYSTEM = 0;
+ public const SAPI = 4;
+
+ /** @var 0|4 */
+ protected int $messageType;
+ protected bool $expandNewlines;
+
+ /**
+ * @param 0|4 $messageType Says where the error should go.
+ * @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
+ *
+ * @throws \InvalidArgumentException If an unsupported message type is set
+ */
+ public function __construct(int $messageType = self::OPERATING_SYSTEM, int|string|Level $level = Level::Debug, bool $bubble = true, bool $expandNewlines = false)
+ {
+ parent::__construct($level, $bubble);
+
+ if (false === \in_array($messageType, self::getAvailableTypes(), true)) {
+ $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
+
+ throw new \InvalidArgumentException($message);
+ }
+
+ $this->messageType = $messageType;
+ $this->expandNewlines = $expandNewlines;
+ }
+
+ /**
+ * @return int[] With all available types
+ */
+ public static function getAvailableTypes(): array
+ {
+ return [
+ self::OPERATING_SYSTEM,
+ self::SAPI,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter('[%datetime%] %channel%.%level_name%: %message% %context% %extra%');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ if (!$this->expandNewlines) {
+ error_log((string) $record->formatted, $this->messageType);
+
+ return;
+ }
+
+ $lines = preg_split('{[\r\n]+}', (string) $record->formatted);
+ if ($lines === false) {
+ $pcreErrorCode = preg_last_error();
+
+ throw new \RuntimeException('Failed to preg_split formatted string: ' . $pcreErrorCode . ' / '. preg_last_error_msg());
+ }
+ foreach ($lines as $line) {
+ error_log($line, $this->messageType);
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php
new file mode 100644
index 0000000..58318be
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FallbackGroupHandler.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Throwable;
+use Monolog\LogRecord;
+
+/**
+ * Forwards records to at most one handler
+ *
+ * If a handler fails, the exception is suppressed and the record is forwarded to the next handler.
+ *
+ * As soon as one handler handles a record successfully, the handling stops there.
+ */
+class FallbackGroupHandler extends GroupHandler
+{
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+ foreach ($this->handlers as $handler) {
+ try {
+ $handler->handle(clone $record);
+ break;
+ } catch (Throwable $e) {
+ // What throwable?
+ }
+ }
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ if (\count($this->processors) > 0) {
+ $processed = [];
+ foreach ($records as $record) {
+ $processed[] = $this->processRecord($record);
+ }
+ $records = $processed;
+ }
+
+ foreach ($this->handlers as $handler) {
+ try {
+ $handler->handleBatch(array_map(fn ($record) => clone $record, $records));
+ break;
+ } catch (Throwable $e) {
+ // What throwable?
+ }
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php
new file mode 100644
index 0000000..6653fa1
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FilterHandler.php
@@ -0,0 +1,202 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Closure;
+use Monolog\Level;
+use Monolog\Logger;
+use Monolog\ResettableInterface;
+use Monolog\Formatter\FormatterInterface;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Simple handler wrapper that filters records based on a list of levels
+ *
+ * It can be configured with an exact list of levels to allow, or a min/max level.
+ *
+ * @author Hennadiy Verkh
+ * @author Jordi Boggiano
+ */
+class FilterHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface
+{
+ use ProcessableHandlerTrait;
+
+ /**
+ * Handler or factory Closure($record, $this)
+ *
+ * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface
+ */
+ protected Closure|HandlerInterface $handler;
+
+ /**
+ * Minimum level for logs that are passed to handler
+ *
+ * @var bool[] Map of Level value => true
+ * @phpstan-var array, true>
+ */
+ protected array $acceptedLevels;
+
+ /**
+ * Whether the messages that are handled can bubble up the stack or not
+ */
+ protected bool $bubble;
+
+ /**
+ * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler
+ *
+ * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $filterHandler).
+ * @param int|string|Level|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
+ * @param int|string|Level|LogLevel::* $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel
+ */
+ public function __construct(Closure|HandlerInterface $handler, int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency, bool $bubble = true)
+ {
+ $this->handler = $handler;
+ $this->bubble = $bubble;
+ $this->setAcceptedLevels($minLevelOrList, $maxLevel);
+ }
+
+ /**
+ * @phpstan-return list List of levels
+ */
+ public function getAcceptedLevels(): array
+ {
+ return array_map(fn (int $level) => Level::from($level), array_keys($this->acceptedLevels));
+ }
+
+ /**
+ * @param int|string|Level|LogLevel::*|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
+ * @param int|string|Level|LogLevel::* $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array
+ * @return $this
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::*|array|value-of|Level|LogLevel::*> $minLevelOrList
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $maxLevel
+ */
+ public function setAcceptedLevels(int|string|Level|array $minLevelOrList = Level::Debug, int|string|Level $maxLevel = Level::Emergency): self
+ {
+ if (\is_array($minLevelOrList)) {
+ $acceptedLevels = array_map(Logger::toMonologLevel(...), $minLevelOrList);
+ } else {
+ $minLevelOrList = Logger::toMonologLevel($minLevelOrList);
+ $maxLevel = Logger::toMonologLevel($maxLevel);
+ $acceptedLevels = array_values(array_filter(Level::cases(), fn (Level $level) => $level->value >= $minLevelOrList->value && $level->value <= $maxLevel->value));
+ }
+ $this->acceptedLevels = [];
+ foreach ($acceptedLevels as $level) {
+ $this->acceptedLevels[$level->value] = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return isset($this->acceptedLevels[$record->level->value]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (!$this->isHandling($record)) {
+ return false;
+ }
+
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+
+ $this->getHandler($record)->handle($record);
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ $filtered = [];
+ foreach ($records as $record) {
+ if ($this->isHandling($record)) {
+ $filtered[] = $record;
+ }
+ }
+
+ if (\count($filtered) > 0) {
+ $this->getHandler($filtered[\count($filtered) - 1])->handleBatch($filtered);
+ }
+ }
+
+ /**
+ * Return the nested handler
+ *
+ * If the handler was provided as a factory, this will trigger the handler's instantiation.
+ */
+ public function getHandler(LogRecord|null $record = null): HandlerInterface
+ {
+ if (!$this->handler instanceof HandlerInterface) {
+ $handler = ($this->handler)($record, $this);
+ if (!$handler instanceof HandlerInterface) {
+ throw new \RuntimeException("The factory Closure should return a HandlerInterface");
+ }
+ $this->handler = $handler;
+ }
+
+ return $this->handler;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ $handler = $this->getHandler();
+ if ($handler instanceof FormattableHandlerInterface) {
+ $handler->setFormatter($formatter);
+
+ return $this;
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ $handler = $this->getHandler();
+ if ($handler instanceof FormattableHandlerInterface) {
+ return $handler->getFormatter();
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
+ }
+
+ public function reset(): void
+ {
+ $this->resetProcessors();
+
+ if ($this->getHandler() instanceof ResettableInterface) {
+ $this->getHandler()->reset();
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php
new file mode 100644
index 0000000..e8a1b0b
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler\FingersCrossed;
+
+use Monolog\LogRecord;
+
+/**
+ * Interface for activation strategies for the FingersCrossedHandler.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ActivationStrategyInterface
+{
+ /**
+ * Returns whether the given record activates the handler.
+ */
+ public function isHandlerActivated(LogRecord $record): bool;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php
new file mode 100644
index 0000000..383e19a
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler\FingersCrossed;
+
+use Monolog\Level;
+use Monolog\Logger;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Channel and Error level based monolog activation strategy. Allows to trigger activation
+ * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
+ * for records of the 'sql' channel; those should trigger activation on level 'WARN'.
+ *
+ * Example:
+ *
+ *
+ * $activationStrategy = new ChannelLevelActivationStrategy(
+ * Level::Critical,
+ * array(
+ * 'request' => Level::Alert,
+ * 'sensitive' => Level::Error,
+ * )
+ * );
+ * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
+ *
+ *
+ * @author Mike Meessen
+ */
+class ChannelLevelActivationStrategy implements ActivationStrategyInterface
+{
+ private Level $defaultActionLevel;
+
+ /**
+ * @var array
+ */
+ private array $channelToActionLevel;
+
+ /**
+ * @param int|string|Level|LogLevel::* $defaultActionLevel The default action level to be used if the record's category doesn't match any
+ * @param array $channelToActionLevel An array that maps channel names to action levels.
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $defaultActionLevel
+ * @phpstan-param array|value-of|Level|LogLevel::*> $channelToActionLevel
+ */
+ public function __construct(int|string|Level $defaultActionLevel, array $channelToActionLevel = [])
+ {
+ $this->defaultActionLevel = Logger::toMonologLevel($defaultActionLevel);
+ $this->channelToActionLevel = array_map(Logger::toMonologLevel(...), $channelToActionLevel);
+ }
+
+ public function isHandlerActivated(LogRecord $record): bool
+ {
+ if (isset($this->channelToActionLevel[$record->channel])) {
+ return $record->level->value >= $this->channelToActionLevel[$record->channel]->value;
+ }
+
+ return $record->level->value >= $this->defaultActionLevel->value;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php
new file mode 100644
index 0000000..c3ca296
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler\FingersCrossed;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+use Monolog\Logger;
+use Psr\Log\LogLevel;
+
+/**
+ * Error level based activation strategy.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ErrorLevelActivationStrategy implements ActivationStrategyInterface
+{
+ private Level $actionLevel;
+
+ /**
+ * @param int|string|Level $actionLevel Level or name or value
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $actionLevel
+ */
+ public function __construct(int|string|Level $actionLevel)
+ {
+ $this->actionLevel = Logger::toMonologLevel($actionLevel);
+ }
+
+ public function isHandlerActivated(LogRecord $record): bool
+ {
+ return $record->level->value >= $this->actionLevel->value;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php
new file mode 100644
index 0000000..1ce64e8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php
@@ -0,0 +1,242 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Closure;
+use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
+use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
+use Monolog\Level;
+use Monolog\Logger;
+use Monolog\ResettableInterface;
+use Monolog\Formatter\FormatterInterface;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Buffers all records until a certain level is reached
+ *
+ * The advantage of this approach is that you don't get any clutter in your log files.
+ * Only requests which actually trigger an error (or whatever your actionLevel is) will be
+ * in the logs, but they will contain all records, not only those above the level threshold.
+ *
+ * You can then have a passthruLevel as well which means that at the end of the request,
+ * even if it did not get activated, it will still send through log records of e.g. at least a
+ * warning level.
+ *
+ * You can find the various activation strategies in the
+ * Monolog\Handler\FingersCrossed\ namespace.
+ *
+ * @author Jordi Boggiano
+ */
+class FingersCrossedHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface, FormattableHandlerInterface
+{
+ use ProcessableHandlerTrait;
+
+ /**
+ * Handler or factory Closure($record, $this)
+ *
+ * @phpstan-var (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface
+ */
+ protected Closure|HandlerInterface $handler;
+
+ protected ActivationStrategyInterface $activationStrategy;
+
+ protected bool $buffering = true;
+
+ protected int $bufferSize;
+
+ /** @var LogRecord[] */
+ protected array $buffer = [];
+
+ protected bool $stopBuffering;
+
+ protected Level|null $passthruLevel = null;
+
+ protected bool $bubble;
+
+ /**
+ * @phpstan-param (Closure(LogRecord|null, HandlerInterface): HandlerInterface)|HandlerInterface $handler
+ *
+ * @param Closure|HandlerInterface $handler Handler or factory Closure($record|null, $fingersCrossedHandler).
+ * @param int|string|Level|LogLevel::*|null $activationStrategy Strategy which determines when this handler takes action, or a level name/value at which the handler is activated
+ * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ * @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true)
+ * @param int|string|Level|LogLevel::*|null $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::*|ActivationStrategyInterface|null $activationStrategy
+ * @phpstan-param value-of|value-of|Level|LogLevel::*|null $passthruLevel
+ */
+ public function __construct(Closure|HandlerInterface $handler, int|string|Level|ActivationStrategyInterface|null $activationStrategy = null, int $bufferSize = 0, bool $bubble = true, bool $stopBuffering = true, int|string|Level|null $passthruLevel = null)
+ {
+ if (null === $activationStrategy) {
+ $activationStrategy = new ErrorLevelActivationStrategy(Level::Warning);
+ }
+
+ // convert simple int activationStrategy to an object
+ if (!$activationStrategy instanceof ActivationStrategyInterface) {
+ $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
+ }
+
+ $this->handler = $handler;
+ $this->activationStrategy = $activationStrategy;
+ $this->bufferSize = $bufferSize;
+ $this->bubble = $bubble;
+ $this->stopBuffering = $stopBuffering;
+
+ if ($passthruLevel !== null) {
+ $this->passthruLevel = Logger::toMonologLevel($passthruLevel);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return true;
+ }
+
+ /**
+ * Manually activate this logger regardless of the activation strategy
+ */
+ public function activate(): void
+ {
+ if ($this->stopBuffering) {
+ $this->buffering = false;
+ }
+
+ $this->getHandler(end($this->buffer) ?: null)->handleBatch($this->buffer);
+ $this->buffer = [];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+
+ if ($this->buffering) {
+ $this->buffer[] = $record;
+ if ($this->bufferSize > 0 && \count($this->buffer) > $this->bufferSize) {
+ array_shift($this->buffer);
+ }
+ if ($this->activationStrategy->isHandlerActivated($record)) {
+ $this->activate();
+ }
+ } else {
+ $this->getHandler($record)->handle($record);
+ }
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function close(): void
+ {
+ $this->flushBuffer();
+
+ $this->getHandler()->close();
+ }
+
+ public function reset(): void
+ {
+ $this->flushBuffer();
+
+ $this->resetProcessors();
+
+ if ($this->getHandler() instanceof ResettableInterface) {
+ $this->getHandler()->reset();
+ }
+ }
+
+ /**
+ * Clears the buffer without flushing any messages down to the wrapped handler.
+ *
+ * It also resets the handler to its initial buffering state.
+ */
+ public function clear(): void
+ {
+ $this->buffer = [];
+ $this->reset();
+ }
+
+ /**
+ * Resets the state of the handler. Stops forwarding records to the wrapped handler.
+ */
+ private function flushBuffer(): void
+ {
+ if (null !== $this->passthruLevel) {
+ $passthruLevel = $this->passthruLevel;
+ $this->buffer = array_filter($this->buffer, static function ($record) use ($passthruLevel) {
+ return $passthruLevel->includes($record->level);
+ });
+ if (\count($this->buffer) > 0) {
+ $this->getHandler(end($this->buffer))->handleBatch($this->buffer);
+ }
+ }
+
+ $this->buffer = [];
+ $this->buffering = true;
+ }
+
+ /**
+ * Return the nested handler
+ *
+ * If the handler was provided as a factory, this will trigger the handler's instantiation.
+ */
+ public function getHandler(LogRecord|null $record = null): HandlerInterface
+ {
+ if (!$this->handler instanceof HandlerInterface) {
+ $handler = ($this->handler)($record, $this);
+ if (!$handler instanceof HandlerInterface) {
+ throw new \RuntimeException("The factory Closure should return a HandlerInterface");
+ }
+ $this->handler = $handler;
+ }
+
+ return $this->handler;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ $handler = $this->getHandler();
+ if ($handler instanceof FormattableHandlerInterface) {
+ $handler->setFormatter($formatter);
+
+ return $this;
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ $handler = $this->getHandler();
+ if ($handler instanceof FormattableHandlerInterface) {
+ return $handler->getFormatter();
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($handler).' does not support formatters.');
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php
new file mode 100644
index 0000000..6b9e510
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php
@@ -0,0 +1,174 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\WildfireFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
+ *
+ * @author Eric Clemmons (@ericclemmons)
+ */
+class FirePHPHandler extends AbstractProcessingHandler
+{
+ use WebRequestRecognizerTrait;
+
+ /**
+ * WildFire JSON header message format
+ */
+ protected const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
+
+ /**
+ * FirePHP structure for parsing messages & their presentation
+ */
+ protected const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
+
+ /**
+ * Must reference a "known" plugin, otherwise headers won't display in FirePHP
+ */
+ protected const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';
+
+ /**
+ * Header prefix for Wildfire to recognize & parse headers
+ */
+ protected const HEADER_PREFIX = 'X-Wf';
+
+ /**
+ * Whether or not Wildfire vendor-specific headers have been generated & sent yet
+ */
+ protected static bool $initialized = false;
+
+ /**
+ * Shared static message index between potentially multiple handlers
+ */
+ protected static int $messageIndex = 1;
+
+ protected static bool $sendHeaders = true;
+
+ /**
+ * Base header creation function used by init headers & record headers
+ *
+ * @param array $meta Wildfire Plugin, Protocol & Structure Indexes
+ * @param string $message Log message
+ *
+ * @return array Complete header string ready for the client as key and message as value
+ *
+ * @phpstan-return non-empty-array
+ */
+ protected function createHeader(array $meta, string $message): array
+ {
+ $header = sprintf('%s-%s', static::HEADER_PREFIX, join('-', $meta));
+
+ return [$header => $message];
+ }
+
+ /**
+ * Creates message header from record
+ *
+ * @return array
+ *
+ * @phpstan-return non-empty-array
+ *
+ * @see createHeader()
+ */
+ protected function createRecordHeader(LogRecord $record): array
+ {
+ // Wildfire is extensible to support multiple protocols & plugins in a single request,
+ // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
+ return $this->createHeader(
+ [1, 1, 1, self::$messageIndex++],
+ $record->formatted
+ );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new WildfireFormatter();
+ }
+
+ /**
+ * Wildfire initialization headers to enable message parsing
+ *
+ * @see createHeader()
+ * @see sendHeader()
+ *
+ * @return array
+ */
+ protected function getInitHeaders(): array
+ {
+ // Initial payload consists of required headers for Wildfire
+ return array_merge(
+ $this->createHeader(['Protocol', 1], static::PROTOCOL_URI),
+ $this->createHeader([1, 'Structure', 1], static::STRUCTURE_URI),
+ $this->createHeader([1, 'Plugin', 1], static::PLUGIN_URI)
+ );
+ }
+
+ /**
+ * Send header string to the client
+ */
+ protected function sendHeader(string $header, string $content): void
+ {
+ if (!headers_sent() && self::$sendHeaders) {
+ header(sprintf('%s: %s', $header, $content));
+ }
+ }
+
+ /**
+ * Creates & sends header for a record, ensuring init headers have been sent prior
+ *
+ * @see sendHeader()
+ * @see sendInitHeaders()
+ */
+ protected function write(LogRecord $record): void
+ {
+ if (!self::$sendHeaders || !$this->isWebRequest()) {
+ return;
+ }
+
+ // WildFire-specific headers must be sent prior to any messages
+ if (!self::$initialized) {
+ self::$initialized = true;
+
+ self::$sendHeaders = $this->headersAccepted();
+ if (!self::$sendHeaders) {
+ return;
+ }
+
+ foreach ($this->getInitHeaders() as $header => $content) {
+ $this->sendHeader($header, $content);
+ }
+ }
+
+ $header = $this->createRecordHeader($record);
+ if (trim(current($header)) !== '') {
+ $this->sendHeader(key($header), current($header));
+ }
+ }
+
+ /**
+ * Verifies if the headers are accepted by the current user agent
+ */
+ protected function headersAccepted(): bool
+ {
+ if (isset($_SERVER['HTTP_USER_AGENT']) && 1 === preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])) {
+ return true;
+ }
+
+ return isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php
new file mode 100644
index 0000000..46ebfc0
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FleepHookHandler.php
@@ -0,0 +1,132 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Sends logs to Fleep.io using Webhook integrations
+ *
+ * You'll need a Fleep.io account to use this handler.
+ *
+ * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
+ * @author Ando Roots
+ */
+class FleepHookHandler extends SocketHandler
+{
+ protected const FLEEP_HOST = 'fleep.io';
+
+ protected const FLEEP_HOOK_URI = '/hook/';
+
+ /**
+ * @var string Webhook token (specifies the conversation where logs are sent)
+ */
+ protected string $token;
+
+ /**
+ * Construct a new Fleep.io Handler.
+ *
+ * For instructions on how to create a new web hook in your conversations
+ * see https://fleep.io/integrations/webhooks/
+ *
+ * @param string $token Webhook token
+ * @throws MissingExtensionException if OpenSSL is missing
+ */
+ public function __construct(
+ string $token,
+ $level = Level::Debug,
+ bool $bubble = true,
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ if (!\extension_loaded('openssl')) {
+ throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FleepHookHandler');
+ }
+
+ $this->token = $token;
+
+ $connectionString = 'ssl://' . static::FLEEP_HOST . ':443';
+ parent::__construct(
+ $connectionString,
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+ }
+
+ /**
+ * Returns the default formatter to use with this handler
+ *
+ * Overloaded to remove empty context and extra arrays from the end of the log message.
+ *
+ * @return LineFormatter
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter(null, null, true, true);
+ }
+
+ /**
+ * Handles a log record
+ */
+ public function write(LogRecord $record): void
+ {
+ parent::write($record);
+ $this->closeSocket();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function generateDataStream(LogRecord $record): string
+ {
+ $content = $this->buildContent($record);
+
+ return $this->buildHeader($content) . $content;
+ }
+
+ /**
+ * Builds the header of the API Call
+ */
+ private function buildHeader(string $content): string
+ {
+ $header = "POST " . static::FLEEP_HOOK_URI . $this->token . " HTTP/1.1\r\n";
+ $header .= "Host: " . static::FLEEP_HOST . "\r\n";
+ $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
+ $header .= "Content-Length: " . \strlen($content) . "\r\n";
+ $header .= "\r\n";
+
+ return $header;
+ }
+
+ /**
+ * Builds the body of API call
+ */
+ private function buildContent(LogRecord $record): string
+ {
+ $dataArray = [
+ 'message' => $record->formatted,
+ ];
+
+ return http_build_query($dataArray);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php
new file mode 100644
index 0000000..27c6c15
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FlowdockHandler.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\Formatter\FlowdockFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Sends notifications through the Flowdock push API
+ *
+ * This must be configured with a FlowdockFormatter instance via setFormatter()
+ *
+ * Notes:
+ * API token - Flowdock API token
+ *
+ * @author Dominik Liebler
+ * @see https://www.flowdock.com/api/push
+ * @deprecated Since 2.9.0 and 3.3.0, Flowdock was shutdown we will thus drop this handler in Monolog 4
+ */
+class FlowdockHandler extends SocketHandler
+{
+ protected string $apiToken;
+
+ /**
+ * @throws MissingExtensionException if OpenSSL is missing
+ */
+ public function __construct(
+ string $apiToken,
+ $level = Level::Debug,
+ bool $bubble = true,
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ if (!\extension_loaded('openssl')) {
+ throw new MissingExtensionException('The OpenSSL PHP extension is required to use the FlowdockHandler');
+ }
+
+ parent::__construct(
+ 'ssl://api.flowdock.com:443',
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+ $this->apiToken = $apiToken;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if (!$formatter instanceof FlowdockFormatter) {
+ throw new \InvalidArgumentException('The FlowdockHandler requires an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
+ }
+
+ return parent::setFormatter($formatter);
+ }
+
+ /**
+ * Gets the default formatter.
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ throw new \InvalidArgumentException('The FlowdockHandler must be configured (via setFormatter) with an instance of Monolog\Formatter\FlowdockFormatter to function correctly');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ parent::write($record);
+
+ $this->closeSocket();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function generateDataStream(LogRecord $record): string
+ {
+ $content = $this->buildContent($record);
+
+ return $this->buildHeader($content) . $content;
+ }
+
+ /**
+ * Builds the body of API call
+ */
+ private function buildContent(LogRecord $record): string
+ {
+ return Utils::jsonEncode($record->formatted);
+ }
+
+ /**
+ * Builds the header of the API Call
+ */
+ private function buildHeader(string $content): string
+ {
+ $header = "POST /v1/messages/team_inbox/" . $this->apiToken . " HTTP/1.1\r\n";
+ $header .= "Host: api.flowdock.com\r\n";
+ $header .= "Content-Type: application/json\r\n";
+ $header .= "Content-Length: " . \strlen($content) . "\r\n";
+ $header .= "\r\n";
+
+ return $header;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php
new file mode 100644
index 0000000..72da59e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+
+/**
+ * Interface to describe loggers that have a formatter
+ *
+ * @author Jordi Boggiano
+ */
+interface FormattableHandlerInterface
+{
+ /**
+ * Sets the formatter.
+ *
+ * @return HandlerInterface self
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface;
+
+ /**
+ * Gets the formatter.
+ */
+ public function getFormatter(): FormatterInterface;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php
new file mode 100644
index 0000000..c044e07
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/FormattableHandlerTrait.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * Helper trait for implementing FormattableInterface
+ *
+ * @author Jordi Boggiano
+ */
+trait FormattableHandlerTrait
+{
+ protected FormatterInterface|null $formatter = null;
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ $this->formatter = $formatter;
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if (null === $this->formatter) {
+ $this->formatter = $this->getDefaultFormatter();
+ }
+
+ return $this->formatter;
+ }
+
+ /**
+ * Gets the default formatter.
+ *
+ * Overwrite this if the LineFormatter is not a good default for your handler.
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php
new file mode 100644
index 0000000..ba5bb97
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Gelf\PublisherInterface;
+use Monolog\Level;
+use Monolog\Formatter\GelfMessageFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Handler to send messages to a Graylog2 (http://www.graylog2.org) server
+ *
+ * @author Matt Lehner
+ * @author Benjamin Zikarsky
+ */
+class GelfHandler extends AbstractProcessingHandler
+{
+ /**
+ * @var PublisherInterface the publisher object that sends the message to the server
+ */
+ protected PublisherInterface $publisher;
+
+ /**
+ * @param PublisherInterface $publisher a gelf publisher object
+ */
+ public function __construct(PublisherInterface $publisher, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+
+ $this->publisher = $publisher;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->publisher->publish($record->formatted);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new GelfMessageFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php
new file mode 100644
index 0000000..0423dc3
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php
@@ -0,0 +1,130 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\ResettableInterface;
+use Monolog\LogRecord;
+
+/**
+ * Forwards records to multiple handlers
+ *
+ * @author Lenar Lõhmus
+ */
+class GroupHandler extends Handler implements ProcessableHandlerInterface, ResettableInterface
+{
+ use ProcessableHandlerTrait;
+
+ /** @var HandlerInterface[] */
+ protected array $handlers;
+ protected bool $bubble;
+
+ /**
+ * @param HandlerInterface[] $handlers Array of Handlers.
+ * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
+ *
+ * @throws \InvalidArgumentException if an unsupported handler is set
+ */
+ public function __construct(array $handlers, bool $bubble = true)
+ {
+ foreach ($handlers as $handler) {
+ if (!$handler instanceof HandlerInterface) {
+ throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
+ }
+ }
+
+ $this->handlers = $handlers;
+ $this->bubble = $bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ foreach ($this->handlers as $handler) {
+ if ($handler->isHandling($record)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (\count($this->processors) > 0) {
+ $record = $this->processRecord($record);
+ }
+
+ foreach ($this->handlers as $handler) {
+ $handler->handle(clone $record);
+ }
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ if (\count($this->processors) > 0) {
+ $processed = [];
+ foreach ($records as $record) {
+ $processed[] = $this->processRecord($record);
+ }
+ $records = $processed;
+ }
+
+ foreach ($this->handlers as $handler) {
+ $handler->handleBatch(array_map(fn ($record) => clone $record, $records));
+ }
+ }
+
+ public function reset(): void
+ {
+ $this->resetProcessors();
+
+ foreach ($this->handlers as $handler) {
+ if ($handler instanceof ResettableInterface) {
+ $handler->reset();
+ }
+ }
+ }
+
+ public function close(): void
+ {
+ parent::close();
+
+ foreach ($this->handlers as $handler) {
+ $handler->close();
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ foreach ($this->handlers as $handler) {
+ if ($handler instanceof FormattableHandlerInterface) {
+ $handler->setFormatter($formatter);
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/Handler.php b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php
new file mode 100644
index 0000000..8a4e7ab
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/Handler.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+/**
+ * Base Handler class providing basic close() support as well as handleBatch
+ *
+ * @author Jordi Boggiano
+ */
+abstract class Handler implements HandlerInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ foreach ($records as $record) {
+ $this->handle($record);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function close(): void
+ {
+ }
+
+ public function __destruct()
+ {
+ try {
+ $this->close();
+ } catch (\Throwable $e) {
+ // do nothing
+ }
+ }
+
+ public function __serialize(): array
+ {
+ $this->close();
+
+ return (array) $this;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php
new file mode 100644
index 0000000..93306d9
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\LogRecord;
+
+/**
+ * Interface that all Monolog Handlers must implement
+ *
+ * @author Jordi Boggiano
+ */
+interface HandlerInterface
+{
+ /**
+ * Checks whether the given record will be handled by this handler.
+ *
+ * This is mostly done for performance reasons, to avoid calling processors for nothing.
+ *
+ * Handlers should still check the record levels within handle(), returning false in isHandling()
+ * is no guarantee that handle() will not be called, and isHandling() might not be called
+ * for a given record.
+ *
+ * @param LogRecord $record Partial log record having only a level initialized
+ */
+ public function isHandling(LogRecord $record): bool;
+
+ /**
+ * Handles a record.
+ *
+ * All records may be passed to this method, and the handler should discard
+ * those that it does not want to handle.
+ *
+ * The return value of this function controls the bubbling process of the handler stack.
+ * Unless the bubbling is interrupted (by returning true), the Logger class will keep on
+ * calling further handlers in the stack with a given log record.
+ *
+ * @param LogRecord $record The record to handle
+ * @return bool true means that this handler handled the record, and that bubbling is not permitted.
+ * false means the record was either not processed or that this handler allows bubbling.
+ */
+ public function handle(LogRecord $record): bool;
+
+ /**
+ * Handles a set of records at once.
+ *
+ * @param array $records The records to handle
+ */
+ public function handleBatch(array $records): void;
+
+ /**
+ * Closes the handler.
+ *
+ * Ends a log cycle and frees all resources used by the handler.
+ *
+ * Closing a Handler means flushing all buffers and freeing any open resources/handles.
+ *
+ * Implementations have to be idempotent (i.e. it should be possible to call close several times without breakage)
+ * and ideally handlers should be able to reopen themselves on handle() after they have been closed.
+ *
+ * This is useful at the end of a request and will be called automatically when the object
+ * is destroyed if you extend Monolog\Handler\Handler.
+ *
+ * If you are thinking of calling this method yourself, most likely you should be
+ * calling ResettableInterface::reset instead. Have a look.
+ */
+ public function close(): void;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php
new file mode 100644
index 0000000..541ec25
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerWrapper.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\ResettableInterface;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * This simple wrapper class can be used to extend handlers functionality.
+ *
+ * Example: A custom filtering that can be applied to any handler.
+ *
+ * Inherit from this class and override handle() like this:
+ *
+ * public function handle(LogRecord $record)
+ * {
+ * if ($record meets certain conditions) {
+ * return false;
+ * }
+ * return $this->handler->handle($record);
+ * }
+ *
+ * @author Alexey Karapetov
+ */
+class HandlerWrapper implements HandlerInterface, ProcessableHandlerInterface, FormattableHandlerInterface, ResettableInterface
+{
+ protected HandlerInterface $handler;
+
+ public function __construct(HandlerInterface $handler)
+ {
+ $this->handler = $handler;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return $this->handler->isHandling($record);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ return $this->handler->handle($record);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ $this->handler->handleBatch($records);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function close(): void
+ {
+ $this->handler->close();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function pushProcessor(callable $callback): HandlerInterface
+ {
+ if ($this->handler instanceof ProcessableHandlerInterface) {
+ $this->handler->pushProcessor($callback);
+
+ return $this;
+ }
+
+ throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function popProcessor(): callable
+ {
+ if ($this->handler instanceof ProcessableHandlerInterface) {
+ return $this->handler->popProcessor();
+ }
+
+ throw new \LogicException('The wrapped handler does not implement ' . ProcessableHandlerInterface::class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ $this->handler->setFormatter($formatter);
+
+ return $this;
+ }
+
+ throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ return $this->handler->getFormatter();
+ }
+
+ throw new \LogicException('The wrapped handler does not implement ' . FormattableHandlerInterface::class);
+ }
+
+ public function reset(): void
+ {
+ if ($this->handler instanceof ResettableInterface) {
+ $this->handler->reset();
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php
new file mode 100644
index 0000000..b9c7ba8
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/IFTTTHandler.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\LogRecord;
+
+/**
+ * IFTTTHandler uses cURL to trigger IFTTT Maker actions
+ *
+ * Register a secret key and trigger/event name at https://ifttt.com/maker
+ *
+ * value1 will be the channel from monolog's Logger constructor,
+ * value2 will be the level name (ERROR, WARNING, ..)
+ * value3 will be the log record's message
+ *
+ * @author Nehal Patel
+ */
+class IFTTTHandler extends AbstractProcessingHandler
+{
+ private string $eventName;
+ private string $secretKey;
+
+ /**
+ * @param string $eventName The name of the IFTTT Maker event that should be triggered
+ * @param string $secretKey A valid IFTTT secret key
+ *
+ * @throws MissingExtensionException If the curl extension is missing
+ */
+ public function __construct(string $eventName, string $secretKey, int|string|Level $level = Level::Error, bool $bubble = true)
+ {
+ if (!\extension_loaded('curl')) {
+ throw new MissingExtensionException('The curl extension is needed to use the IFTTTHandler');
+ }
+
+ $this->eventName = $eventName;
+ $this->secretKey = $secretKey;
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function write(LogRecord $record): void
+ {
+ $postData = [
+ "value1" => $record->channel,
+ "value2" => $record["level_name"],
+ "value3" => $record->message,
+ ];
+ $postString = Utils::jsonEncode($postData);
+
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, "https://maker.ifttt.com/trigger/" . $this->eventName . "/with/key/" . $this->secretKey);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Content-Type: application/json",
+ ]);
+
+ Curl\Util::execute($ch);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php
new file mode 100644
index 0000000..4b558bd
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/InsightOpsHandler.php
@@ -0,0 +1,74 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Inspired on LogEntriesHandler.
+ *
+ * @author Robert Kaufmann III
+ * @author Gabriel Machado
+ */
+class InsightOpsHandler extends SocketHandler
+{
+ protected string $logToken;
+
+ /**
+ * @param string $token Log token supplied by InsightOps
+ * @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.
+ * @param bool $useSSL Whether or not SSL encryption should be used
+ *
+ * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
+ */
+ public function __construct(
+ string $token,
+ string $region = 'us',
+ bool $useSSL = true,
+ $level = Level::Debug,
+ bool $bubble = true,
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ if ($useSSL && !\extension_loaded('openssl')) {
+ throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for InsightOpsHandler');
+ }
+
+ $endpoint = $useSSL
+ ? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'
+ : $region . '.data.logs.insight.rapid7.com:80';
+
+ parent::__construct(
+ $endpoint,
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+ $this->logToken = $token;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function generateDataStream(LogRecord $record): string
+ {
+ return $this->logToken . ' ' . $record->formatted;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php
new file mode 100644
index 0000000..8c12898
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/LogEntriesHandler.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * @author Robert Kaufmann III
+ */
+class LogEntriesHandler extends SocketHandler
+{
+ protected string $logToken;
+
+ /**
+ * @param string $token Log token supplied by LogEntries
+ * @param bool $useSSL Whether or not SSL encryption should be used.
+ * @param string $host Custom hostname to send the data to if needed
+ *
+ * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
+ */
+ public function __construct(
+ string $token,
+ bool $useSSL = true,
+ $level = Level::Debug,
+ bool $bubble = true,
+ string $host = 'data.logentries.com',
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ if ($useSSL && !\extension_loaded('openssl')) {
+ throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
+ }
+
+ $endpoint = $useSSL ? 'ssl://' . $host . ':443' : $host . ':80';
+ parent::__construct(
+ $endpoint,
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+ $this->logToken = $token;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function generateDataStream(LogRecord $record): string
+ {
+ return $this->logToken . ' ' . $record->formatted;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php
new file mode 100644
index 0000000..c1ccc0e
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/LogglyHandler.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LogglyFormatter;
+use CurlHandle;
+use Monolog\LogRecord;
+
+/**
+ * Sends errors to Loggly.
+ *
+ * @author Przemek Sobstel
+ * @author Adam Pancutt
+ * @author Gregory Barchard
+ */
+class LogglyHandler extends AbstractProcessingHandler
+{
+ protected const HOST = 'logs-01.loggly.com';
+ protected const ENDPOINT_SINGLE = 'inputs';
+ protected const ENDPOINT_BATCH = 'bulk';
+
+ /**
+ * Caches the curl handlers for every given endpoint.
+ *
+ * @var CurlHandle[]
+ */
+ protected array $curlHandlers = [];
+
+ protected string $token;
+
+ /** @var string[] */
+ protected array $tag = [];
+
+ /**
+ * @param string $token API token supplied by Loggly
+ *
+ * @throws MissingExtensionException If the curl extension is missing
+ */
+ public function __construct(string $token, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ if (!\extension_loaded('curl')) {
+ throw new MissingExtensionException('The curl extension is needed to use the LogglyHandler');
+ }
+
+ $this->token = $token;
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * Loads and returns the shared curl handler for the given endpoint.
+ */
+ protected function getCurlHandler(string $endpoint): CurlHandle
+ {
+ if (!\array_key_exists($endpoint, $this->curlHandlers)) {
+ $this->curlHandlers[$endpoint] = $this->loadCurlHandle($endpoint);
+ }
+
+ return $this->curlHandlers[$endpoint];
+ }
+
+ /**
+ * Starts a fresh curl session for the given endpoint and returns its handler.
+ */
+ private function loadCurlHandle(string $endpoint): CurlHandle
+ {
+ $url = sprintf("https://%s/%s/%s/", static::HOST, $endpoint, $this->token);
+
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_URL, $url);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+ return $ch;
+ }
+
+ /**
+ * @param string[]|string $tag
+ * @return $this
+ */
+ public function setTag(string|array $tag): self
+ {
+ if ('' === $tag || [] === $tag) {
+ $this->tag = [];
+ } else {
+ $this->tag = \is_array($tag) ? $tag : [$tag];
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param string[]|string $tag
+ * @return $this
+ */
+ public function addTag(string|array $tag): self
+ {
+ if ('' !== $tag) {
+ $tag = \is_array($tag) ? $tag : [$tag];
+ $this->tag = array_unique(array_merge($this->tag, $tag));
+ }
+
+ return $this;
+ }
+
+ protected function write(LogRecord $record): void
+ {
+ $this->send($record->formatted, static::ENDPOINT_SINGLE);
+ }
+
+ public function handleBatch(array $records): void
+ {
+ $level = $this->level;
+
+ $records = array_filter($records, function ($record) use ($level) {
+ return ($record->level->value >= $level->value);
+ });
+
+ if (\count($records) > 0) {
+ $this->send($this->getFormatter()->formatBatch($records), static::ENDPOINT_BATCH);
+ }
+ }
+
+ protected function send(string $data, string $endpoint): void
+ {
+ $ch = $this->getCurlHandler($endpoint);
+
+ $headers = ['Content-Type: application/json'];
+
+ if (\count($this->tag) > 0) {
+ $headers[] = 'X-LOGGLY-TAG: '.implode(',', $this->tag);
+ }
+
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+
+ Curl\Util::execute($ch, 5);
+ }
+
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LogglyFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php
new file mode 100644
index 0000000..6aa1b31
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/LogmaticHandler.php
@@ -0,0 +1,98 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\LogmaticFormatter;
+use Monolog\LogRecord;
+
+/**
+ * @author Julien Breux
+ */
+class LogmaticHandler extends SocketHandler
+{
+ private string $logToken;
+
+ private string $hostname;
+
+ private string $appName;
+
+ /**
+ * @param string $token Log token supplied by Logmatic.
+ * @param string $hostname Host name supplied by Logmatic.
+ * @param string $appName Application name supplied by Logmatic.
+ * @param bool $useSSL Whether or not SSL encryption should be used.
+ *
+ * @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
+ */
+ public function __construct(
+ string $token,
+ string $hostname = '',
+ string $appName = '',
+ bool $useSSL = true,
+ $level = Level::Debug,
+ bool $bubble = true,
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ if ($useSSL && !\extension_loaded('openssl')) {
+ throw new MissingExtensionException('The OpenSSL PHP extension is required to use SSL encrypted connection for LogmaticHandler');
+ }
+
+ $endpoint = $useSSL ? 'ssl://api.logmatic.io:10515' : 'api.logmatic.io:10514';
+ $endpoint .= '/v1/';
+
+ parent::__construct(
+ $endpoint,
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+
+ $this->logToken = $token;
+ $this->hostname = $hostname;
+ $this->appName = $appName;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function generateDataStream(LogRecord $record): string
+ {
+ return $this->logToken . ' ' . $record->formatted;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ $formatter = new LogmaticFormatter();
+
+ if ($this->hostname !== '') {
+ $formatter->setHostname($this->hostname);
+ }
+ if ($this->appName !== '') {
+ $formatter->setAppName($this->appName);
+ }
+
+ return $formatter;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php
new file mode 100644
index 0000000..b6c8227
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php
@@ -0,0 +1,91 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\HtmlFormatter;
+use Monolog\LogRecord;
+
+/**
+ * Base class for all mail handlers
+ *
+ * @author Gyula Sallai
+ */
+abstract class MailHandler extends AbstractProcessingHandler
+{
+ /**
+ * @inheritDoc
+ */
+ public function handleBatch(array $records): void
+ {
+ $messages = [];
+
+ foreach ($records as $record) {
+ if ($record->level->isLowerThan($this->level)) {
+ continue;
+ }
+
+ $message = $this->processRecord($record);
+ $messages[] = $message;
+ }
+
+ if (\count($messages) > 0) {
+ $this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
+ }
+ }
+
+ /**
+ * Send a mail with the given content
+ *
+ * @param string $content formatted email body to be sent
+ * @param array $records the array of log records that formed this content
+ *
+ * @phpstan-param non-empty-array $records
+ */
+ abstract protected function send(string $content, array $records): void;
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->send((string) $record->formatted, [$record]);
+ }
+
+ /**
+ * @phpstan-param non-empty-array $records
+ */
+ protected function getHighestRecord(array $records): LogRecord
+ {
+ $highestRecord = null;
+ foreach ($records as $record) {
+ if ($highestRecord === null || $record->level->isHigherThan($highestRecord->level)) {
+ $highestRecord = $record;
+ }
+ }
+
+ return $highestRecord;
+ }
+
+ protected function isHtmlBody(string $body): bool
+ {
+ return ($body[0] ?? null) === '<';
+ }
+
+ /**
+ * Gets the default formatter.
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new HtmlFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php
new file mode 100644
index 0000000..477ac34
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/MandrillHandler.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Swift;
+use Swift_Message;
+
+/**
+ * MandrillHandler uses cURL to send the emails to the Mandrill API
+ *
+ * @author Adam Nicholson
+ */
+class MandrillHandler extends MailHandler
+{
+ protected Swift_Message $message;
+ protected string $apiKey;
+
+ /**
+ * @phpstan-param (Swift_Message|callable(): Swift_Message) $message
+ *
+ * @param string $apiKey A valid Mandrill API key
+ * @param callable|Swift_Message $message An example message for real messages, only the body will be replaced
+ *
+ * @throws \InvalidArgumentException if not a Swift Message is set
+ */
+ public function __construct(string $apiKey, callable|Swift_Message $message, int|string|Level $level = Level::Error, bool $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+
+ if (!$message instanceof Swift_Message) {
+ $message = $message();
+ }
+ if (!$message instanceof Swift_Message) {
+ throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
+ }
+ $this->message = $message;
+ $this->apiKey = $apiKey;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function send(string $content, array $records): void
+ {
+ $mime = 'text/plain';
+ if ($this->isHtmlBody($content)) {
+ $mime = 'text/html';
+ }
+
+ $message = clone $this->message;
+ $message->setBody($content, $mime);
+ /** @phpstan-ignore-next-line */
+ if (version_compare(Swift::VERSION, '6.0.0', '>=')) {
+ $message->setDate(new \DateTimeImmutable());
+ } else {
+ /** @phpstan-ignore-next-line */
+ $message->setDate(time());
+ }
+
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_URL, 'https://mandrillapp.com/api/1.0/messages/send-raw.json');
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
+ 'key' => $this->apiKey,
+ 'raw_message' => (string) $message,
+ 'async' => false,
+ ]));
+
+ Curl\Util::execute($ch);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php
new file mode 100644
index 0000000..3965aee
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+/**
+ * Exception can be thrown if an extension for a handler is missing
+ *
+ * @author Christian Bergau
+ */
+class MissingExtensionException extends \Exception
+{
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php
new file mode 100644
index 0000000..3a1c085
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use MongoDB\Client;
+use MongoDB\Collection;
+use MongoDB\Driver\BulkWrite;
+use MongoDB\Driver\Manager;
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Formatter\MongoDBFormatter;
+use Monolog\LogRecord;
+
+/**
+ * Logs to a MongoDB database.
+ *
+ * Usage example:
+ *
+ * $log = new \Monolog\Logger('application');
+ * $client = new \MongoDB\Client('mongodb://localhost:27017');
+ * $mongodb = new \Monolog\Handler\MongoDBHandler($client, 'logs', 'prod');
+ * $log->pushHandler($mongodb);
+ *
+ * The above examples uses the MongoDB PHP library's client class; however, the
+ * MongoDB\Driver\Manager class from ext-mongodb is also supported.
+ */
+class MongoDBHandler extends AbstractProcessingHandler
+{
+ private Collection $collection;
+
+ private Client|Manager $manager;
+
+ private string|null $namespace = null;
+
+ /**
+ * Constructor.
+ *
+ * @param Client|Manager $mongodb MongoDB library or driver client
+ * @param string $database Database name
+ * @param string $collection Collection name
+ */
+ public function __construct(Client|Manager $mongodb, string $database, string $collection, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ if ($mongodb instanceof Client) {
+ $this->collection = method_exists($mongodb, 'getCollection') ? $mongodb->getCollection($database, $collection) : $mongodb->selectCollection($database, $collection);
+ } else {
+ $this->manager = $mongodb;
+ $this->namespace = $database . '.' . $collection;
+ }
+
+ parent::__construct($level, $bubble);
+ }
+
+ protected function write(LogRecord $record): void
+ {
+ if (isset($this->collection)) {
+ $this->collection->insertOne($record->formatted);
+ }
+
+ if (isset($this->manager, $this->namespace)) {
+ $bulk = new BulkWrite;
+ $bulk->insert($record->formatted);
+ $this->manager->executeBulkWrite($this->namespace, $bulk);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new MongoDBFormatter;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php
new file mode 100644
index 0000000..a5d1a97
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php
@@ -0,0 +1,179 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * NativeMailerHandler uses the mail() function to send the emails
+ *
+ * @author Christophe Coevoet
+ * @author Mark Garrett
+ */
+class NativeMailerHandler extends MailHandler
+{
+ /**
+ * The email addresses to which the message will be sent
+ * @var string[]
+ */
+ protected array $to;
+
+ /**
+ * The subject of the email
+ */
+ protected string $subject;
+
+ /**
+ * Optional headers for the message
+ * @var string[]
+ */
+ protected array $headers = [];
+
+ /**
+ * Optional parameters for the message
+ * @var string[]
+ */
+ protected array $parameters = [];
+
+ /**
+ * The wordwrap length for the message
+ */
+ protected int $maxColumnWidth;
+
+ /**
+ * The Content-type for the message
+ */
+ protected string|null $contentType = null;
+
+ /**
+ * The encoding for the message
+ */
+ protected string $encoding = 'utf-8';
+
+ /**
+ * @param string|string[] $to The receiver of the mail
+ * @param string $subject The subject of the mail
+ * @param string $from The sender of the mail
+ * @param int $maxColumnWidth The maximum column width that the message lines will have
+ */
+ public function __construct(string|array $to, string $subject, string $from, int|string|Level $level = Level::Error, bool $bubble = true, int $maxColumnWidth = 70)
+ {
+ parent::__construct($level, $bubble);
+ $this->to = (array) $to;
+ $this->subject = $subject;
+ $this->addHeader(sprintf('From: %s', $from));
+ $this->maxColumnWidth = $maxColumnWidth;
+ }
+
+ /**
+ * Add headers to the message
+ *
+ * @param string|string[] $headers Custom added headers
+ * @return $this
+ */
+ public function addHeader($headers): self
+ {
+ foreach ((array) $headers as $header) {
+ if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) {
+ throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons');
+ }
+ $this->headers[] = $header;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add parameters to the message
+ *
+ * @param string|string[] $parameters Custom added parameters
+ * @return $this
+ */
+ public function addParameter($parameters): self
+ {
+ $this->parameters = array_merge($this->parameters, (array) $parameters);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function send(string $content, array $records): void
+ {
+ $contentType = $this->getContentType() ?? ($this->isHtmlBody($content) ? 'text/html' : 'text/plain');
+
+ if ($contentType !== 'text/html') {
+ $content = wordwrap($content, $this->maxColumnWidth);
+ }
+
+ $headers = ltrim(implode("\r\n", $this->headers) . "\r\n", "\r\n");
+ $headers .= 'Content-type: ' . $contentType . '; charset=' . $this->getEncoding() . "\r\n";
+ if ($contentType === 'text/html' && false === strpos($headers, 'MIME-Version:')) {
+ $headers .= 'MIME-Version: 1.0' . "\r\n";
+ }
+
+ $subjectFormatter = new LineFormatter($this->subject);
+ $subject = $subjectFormatter->format($this->getHighestRecord($records));
+
+ $parameters = implode(' ', $this->parameters);
+ foreach ($this->to as $to) {
+ $this->mail($to, $subject, $content, $headers, $parameters);
+ }
+ }
+
+ public function getContentType(): ?string
+ {
+ return $this->contentType;
+ }
+
+ public function getEncoding(): string
+ {
+ return $this->encoding;
+ }
+
+ /**
+ * @param string $contentType The content type of the email - Defaults to text/plain. Use text/html for HTML messages.
+ * @return $this
+ */
+ public function setContentType(string $contentType): self
+ {
+ if (strpos($contentType, "\n") !== false || strpos($contentType, "\r") !== false) {
+ throw new \InvalidArgumentException('The content type can not contain newline characters to prevent email header injection');
+ }
+
+ $this->contentType = $contentType;
+
+ return $this;
+ }
+
+ /**
+ * @return $this
+ */
+ public function setEncoding(string $encoding): self
+ {
+ if (strpos($encoding, "\n") !== false || strpos($encoding, "\r") !== false) {
+ throw new \InvalidArgumentException('The encoding can not contain newline characters to prevent email header injection');
+ }
+
+ $this->encoding = $encoding;
+
+ return $this;
+ }
+
+
+ protected function mail(string $to, string $subject, string $content, string $headers, string $parameters): void
+ {
+ mail($to, $subject, $content, $headers, $parameters);
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php
new file mode 100644
index 0000000..4f28dd4
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Utils;
+use Monolog\Formatter\NormalizerFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Class to record a log on a NewRelic application.
+ * Enabling New Relic High Security mode may prevent capture of useful information.
+ *
+ * This handler requires a NormalizerFormatter to function and expects an array in $record->formatted
+ *
+ * @see https://docs.newrelic.com/docs/agents/php-agent
+ * @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
+ */
+class NewRelicHandler extends AbstractProcessingHandler
+{
+ /**
+ * @inheritDoc
+ */
+ public function __construct(
+ int|string|Level $level = Level::Error,
+ bool $bubble = true,
+
+ /**
+ * Name of the New Relic application that will receive logs from this handler.
+ */
+ protected string|null $appName = null,
+
+ /**
+ * Some context and extra data is passed into the handler as arrays of values. Do we send them as is
+ * (useful if we are using the API), or explode them for display on the NewRelic RPM website?
+ */
+ protected bool $explodeArrays = false,
+
+ /**
+ * Name of the current transaction
+ */
+ protected string|null $transactionName = null
+ ) {
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ if (!$this->isNewRelicEnabled()) {
+ throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
+ }
+
+ if (null !== ($appName = $this->getAppName($record->context))) {
+ $this->setNewRelicAppName($appName);
+ }
+
+ if (null !== ($transactionName = $this->getTransactionName($record->context))) {
+ $this->setNewRelicTransactionName($transactionName);
+ unset($record->formatted['context']['transaction_name']);
+ }
+
+ if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) {
+ newrelic_notice_error($record->message, $record->context['exception']);
+ unset($record->formatted['context']['exception']);
+ } else {
+ newrelic_notice_error($record->message);
+ }
+
+ if (isset($record->formatted['context']) && \is_array($record->formatted['context'])) {
+ foreach ($record->formatted['context'] as $key => $parameter) {
+ if (\is_array($parameter) && $this->explodeArrays) {
+ foreach ($parameter as $paramKey => $paramValue) {
+ $this->setNewRelicParameter('context_' . $key . '_' . $paramKey, $paramValue);
+ }
+ } else {
+ $this->setNewRelicParameter('context_' . $key, $parameter);
+ }
+ }
+ }
+
+ if (isset($record->formatted['extra']) && \is_array($record->formatted['extra'])) {
+ foreach ($record->formatted['extra'] as $key => $parameter) {
+ if (\is_array($parameter) && $this->explodeArrays) {
+ foreach ($parameter as $paramKey => $paramValue) {
+ $this->setNewRelicParameter('extra_' . $key . '_' . $paramKey, $paramValue);
+ }
+ } else {
+ $this->setNewRelicParameter('extra_' . $key, $parameter);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether the NewRelic extension is enabled in the system.
+ */
+ protected function isNewRelicEnabled(): bool
+ {
+ return \extension_loaded('newrelic');
+ }
+
+ /**
+ * Returns the appname where this log should be sent. Each log can override the default appname, set in this
+ * handler's constructor, by providing the appname in it's context.
+ *
+ * @param mixed[] $context
+ */
+ protected function getAppName(array $context): ?string
+ {
+ if (isset($context['appname'])) {
+ return $context['appname'];
+ }
+
+ return $this->appName;
+ }
+
+ /**
+ * Returns the name of the current transaction. Each log can override the default transaction name, set in this
+ * handler's constructor, by providing the transaction_name in it's context
+ *
+ * @param mixed[] $context
+ */
+ protected function getTransactionName(array $context): ?string
+ {
+ if (isset($context['transaction_name'])) {
+ return $context['transaction_name'];
+ }
+
+ return $this->transactionName;
+ }
+
+ /**
+ * Sets the NewRelic application that should receive this log.
+ */
+ protected function setNewRelicAppName(string $appName): void
+ {
+ newrelic_set_appname($appName);
+ }
+
+ /**
+ * Overwrites the name of the current transaction
+ */
+ protected function setNewRelicTransactionName(string $transactionName): void
+ {
+ newrelic_name_transaction($transactionName);
+ }
+
+ /**
+ * @param mixed $value
+ */
+ protected function setNewRelicParameter(string $key, $value): void
+ {
+ if (null === $value || \is_scalar($value)) {
+ newrelic_add_custom_parameter($key, $value);
+ } else {
+ newrelic_add_custom_parameter($key, Utils::jsonEncode($value, null, true));
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new NormalizerFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php
new file mode 100644
index 0000000..d9fea18
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/NoopHandler.php
@@ -0,0 +1,42 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\LogRecord;
+
+/**
+ * No-op
+ *
+ * This handler handles anything, but does nothing, and does not stop bubbling to the rest of the stack.
+ * This can be used for testing, or to disable a handler when overriding a configuration without
+ * influencing the rest of the stack.
+ *
+ * @author Roel Harbers
+ */
+class NoopHandler extends Handler
+{
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php
new file mode 100644
index 0000000..1aa84e4
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Psr\Log\LogLevel;
+use Monolog\Logger;
+use Monolog\LogRecord;
+
+/**
+ * Blackhole
+ *
+ * Any record it can handle will be thrown away. This can be used
+ * to put on top of an existing stack to override it temporarily.
+ *
+ * @author Jordi Boggiano
+ */
+class NullHandler extends Handler
+{
+ private Level $level;
+
+ /**
+ * @param string|int|Level $level The minimum logging level at which this handler will be triggered
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $level
+ */
+ public function __construct(string|int|Level $level = Level::Debug)
+ {
+ $this->level = Logger::toMonologLevel($level);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function isHandling(LogRecord $record): bool
+ {
+ return $record->level->value >= $this->level->value;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ return $record->level->value >= $this->level->value;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php
new file mode 100644
index 0000000..adc0eb1
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/OverflowHandler.php
@@ -0,0 +1,139 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Handler to only pass log messages when a certain threshold of number of messages is reached.
+ *
+ * This can be useful in cases of processing a batch of data, but you're for example only interested
+ * in case it fails catastrophically instead of a warning for 1 or 2 events. Worse things can happen, right?
+ *
+ * Usage example:
+ *
+ * ```
+ * $log = new Logger('application');
+ * $handler = new SomeHandler(...)
+ *
+ * // Pass all warnings to the handler when more than 10 & all error messages when more then 5
+ * $overflow = new OverflowHandler($handler, [Level::Warning->value => 10, Level::Error->value => 5]);
+ *
+ * $log->pushHandler($overflow);
+ *```
+ *
+ * @author Kris Buist
+ */
+class OverflowHandler extends AbstractHandler implements FormattableHandlerInterface
+{
+ private HandlerInterface $handler;
+
+ /** @var array */
+ private array $thresholdMap = [];
+
+ /**
+ * Buffer of all messages passed to the handler before the threshold was reached
+ *
+ * @var mixed[][]
+ */
+ private array $buffer = [];
+
+ /**
+ * @param array $thresholdMap Dictionary of log level value => threshold
+ */
+ public function __construct(
+ HandlerInterface $handler,
+ array $thresholdMap = [],
+ $level = Level::Debug,
+ bool $bubble = true
+ ) {
+ $this->handler = $handler;
+ foreach ($thresholdMap as $thresholdLevel => $threshold) {
+ $this->thresholdMap[$thresholdLevel] = $threshold;
+ }
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * Handles a record.
+ *
+ * All records may be passed to this method, and the handler should discard
+ * those that it does not want to handle.
+ *
+ * The return value of this function controls the bubbling process of the handler stack.
+ * Unless the bubbling is interrupted (by returning true), the Logger class will keep on
+ * calling further handlers in the stack with a given log record.
+ *
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if ($record->level->isLowerThan($this->level)) {
+ return false;
+ }
+
+ $level = $record->level->value;
+
+ if (!isset($this->thresholdMap[$level])) {
+ $this->thresholdMap[$level] = 0;
+ }
+
+ if ($this->thresholdMap[$level] > 0) {
+ // The overflow threshold is not yet reached, so we're buffering the record and lowering the threshold by 1
+ $this->thresholdMap[$level]--;
+ $this->buffer[$level][] = $record;
+
+ return false === $this->bubble;
+ }
+
+ if ($this->thresholdMap[$level] === 0) {
+ // This current message is breaking the threshold. Flush the buffer and continue handling the current record
+ foreach ($this->buffer[$level] ?? [] as $buffered) {
+ $this->handler->handle($buffered);
+ }
+ $this->thresholdMap[$level]--;
+ unset($this->buffer[$level]);
+ }
+
+ $this->handler->handle($record);
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ $this->handler->setFormatter($formatter);
+
+ return $this;
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if ($this->handler instanceof FormattableHandlerInterface) {
+ return $this->handler->getFormatter();
+ }
+
+ throw new \UnexpectedValueException('The nested handler of type '.\get_class($this->handler).' does not support formatters.');
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php
new file mode 100644
index 0000000..b37266d
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/PHPConsoleHandler.php
@@ -0,0 +1,303 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Level;
+use Monolog\Utils;
+use PhpConsole\Connector;
+use PhpConsole\Handler as VendorPhpConsoleHandler;
+use PhpConsole\Helper;
+use Monolog\LogRecord;
+use PhpConsole\Storage;
+
+/**
+ * Monolog handler for Google Chrome extension "PHP Console"
+ *
+ * Display PHP error/debug log messages in Google Chrome console and notification popups, executes PHP code remotely
+ *
+ * Usage:
+ * 1. Install Google Chrome extension [now dead and removed from the chrome store]
+ * 2. See overview https://github.com/barbushin/php-console#overview
+ * 3. Install PHP Console library https://github.com/barbushin/php-console#installation
+ * 4. Example (result will looks like http://i.hizliresim.com/vg3Pz4.png)
+ *
+ * $logger = new \Monolog\Logger('all', array(new \Monolog\Handler\PHPConsoleHandler()));
+ * \Monolog\ErrorHandler::register($logger);
+ * echo $undefinedVar;
+ * $logger->debug('SELECT * FROM users', array('db', 'time' => 0.012));
+ * PC::debug($_SERVER); // PHP Console debugger for any type of vars
+ *
+ * @author Sergey Barbushin https://www.linkedin.com/in/barbushin
+ * @phpstan-type Options array{
+ * enabled: bool,
+ * classesPartialsTraceIgnore: string[],
+ * debugTagsKeysInContext: array,
+ * useOwnErrorsHandler: bool,
+ * useOwnExceptionsHandler: bool,
+ * sourcesBasePath: string|null,
+ * registerHelper: bool,
+ * serverEncoding: string|null,
+ * headersLimit: int|null,
+ * password: string|null,
+ * enableSslOnlyMode: bool,
+ * ipMasks: string[],
+ * enableEvalListener: bool,
+ * dumperDetectCallbacks: bool,
+ * dumperLevelLimit: int,
+ * dumperItemsCountLimit: int,
+ * dumperItemSizeLimit: int,
+ * dumperDumpSizeLimit: int,
+ * detectDumpTraceAndSource: bool,
+ * dataStorage: Storage|null
+ * }
+ * @phpstan-type InputOptions array{
+ * enabled?: bool,
+ * classesPartialsTraceIgnore?: string[],
+ * debugTagsKeysInContext?: array,
+ * useOwnErrorsHandler?: bool,
+ * useOwnExceptionsHandler?: bool,
+ * sourcesBasePath?: string|null,
+ * registerHelper?: bool,
+ * serverEncoding?: string|null,
+ * headersLimit?: int|null,
+ * password?: string|null,
+ * enableSslOnlyMode?: bool,
+ * ipMasks?: string[],
+ * enableEvalListener?: bool,
+ * dumperDetectCallbacks?: bool,
+ * dumperLevelLimit?: int,
+ * dumperItemsCountLimit?: int,
+ * dumperItemSizeLimit?: int,
+ * dumperDumpSizeLimit?: int,
+ * detectDumpTraceAndSource?: bool,
+ * dataStorage?: Storage|null
+ * }
+ *
+ * @deprecated Since 2.8.0 and 3.2.0, PHPConsole is abandoned and thus we will drop this handler in Monolog 4
+ */
+class PHPConsoleHandler extends AbstractProcessingHandler
+{
+ /**
+ * @phpstan-var Options
+ */
+ private array $options = [
+ 'enabled' => true, // bool Is PHP Console server enabled
+ 'classesPartialsTraceIgnore' => ['Monolog\\'], // array Hide calls of classes started with...
+ 'debugTagsKeysInContext' => [0, 'tag'], // bool Is PHP Console server enabled
+ 'useOwnErrorsHandler' => false, // bool Enable errors handling
+ 'useOwnExceptionsHandler' => false, // bool Enable exceptions handling
+ 'sourcesBasePath' => null, // string Base path of all project sources to strip in errors source paths
+ 'registerHelper' => true, // bool Register PhpConsole\Helper that allows short debug calls like PC::debug($var, 'ta.g.s')
+ 'serverEncoding' => null, // string|null Server internal encoding
+ 'headersLimit' => null, // int|null Set headers size limit for your web-server
+ 'password' => null, // string|null Protect PHP Console connection by password
+ 'enableSslOnlyMode' => false, // bool Force connection by SSL for clients with PHP Console installed
+ 'ipMasks' => [], // array Set IP masks of clients that will be allowed to connect to PHP Console: array('192.168.*.*', '127.0.0.1')
+ 'enableEvalListener' => false, // bool Enable eval request to be handled by eval dispatcher(if enabled, 'password' option is also required)
+ 'dumperDetectCallbacks' => false, // bool Convert callback items in dumper vars to (callback SomeClass::someMethod) strings
+ 'dumperLevelLimit' => 5, // int Maximum dumped vars array or object nested dump level
+ 'dumperItemsCountLimit' => 100, // int Maximum dumped var same level array items or object properties number
+ 'dumperItemSizeLimit' => 5000, // int Maximum length of any string or dumped array item
+ 'dumperDumpSizeLimit' => 500000, // int Maximum approximate size of dumped vars result formatted in JSON
+ 'detectDumpTraceAndSource' => false, // bool Autodetect and append trace data to debug
+ 'dataStorage' => null, // \PhpConsole\Storage|null Fixes problem with custom $_SESSION handler (see https://github.com/barbushin/php-console#troubleshooting-with-_session-handler-overridden-in-some-frameworks)
+ ];
+
+ private Connector $connector;
+
+ /**
+ * @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details
+ * @param Connector|null $connector Instance of \PhpConsole\Connector class (optional)
+ * @throws \RuntimeException
+ * @phpstan-param InputOptions $options
+ */
+ public function __construct(array $options = [], ?Connector $connector = null, int|string|Level $level = Level::Debug, bool $bubble = true)
+ {
+ if (!class_exists('PhpConsole\Connector')) {
+ throw new \RuntimeException('PHP Console library not found. See https://github.com/barbushin/php-console#installation');
+ }
+ parent::__construct($level, $bubble);
+ $this->options = $this->initOptions($options);
+ $this->connector = $this->initConnector($connector);
+ }
+
+ /**
+ * @param array $options
+ * @return array
+ *
+ * @phpstan-param InputOptions $options
+ * @phpstan-return Options
+ */
+ private function initOptions(array $options): array
+ {
+ $wrongOptions = array_diff(array_keys($options), array_keys($this->options));
+ if (\count($wrongOptions) > 0) {
+ throw new \RuntimeException('Unknown options: ' . implode(', ', $wrongOptions));
+ }
+
+ return array_replace($this->options, $options);
+ }
+
+ private function initConnector(?Connector $connector = null): Connector
+ {
+ if (null === $connector) {
+ if ($this->options['dataStorage'] instanceof Storage) {
+ Connector::setPostponeStorage($this->options['dataStorage']);
+ }
+ $connector = Connector::getInstance();
+ }
+
+ if ($this->options['registerHelper'] && !Helper::isRegistered()) {
+ Helper::register();
+ }
+
+ if ($this->options['enabled'] && $connector->isActiveClient()) {
+ if ($this->options['useOwnErrorsHandler'] || $this->options['useOwnExceptionsHandler']) {
+ $handler = VendorPhpConsoleHandler::getInstance();
+ $handler->setHandleErrors($this->options['useOwnErrorsHandler']);
+ $handler->setHandleExceptions($this->options['useOwnExceptionsHandler']);
+ $handler->start();
+ }
+ if (null !== $this->options['sourcesBasePath']) {
+ $connector->setSourcesBasePath($this->options['sourcesBasePath']);
+ }
+ if (null !== $this->options['serverEncoding']) {
+ $connector->setServerEncoding($this->options['serverEncoding']);
+ }
+ if (null !== $this->options['password']) {
+ $connector->setPassword($this->options['password']);
+ }
+ if ($this->options['enableSslOnlyMode']) {
+ $connector->enableSslOnlyMode();
+ }
+ if (\count($this->options['ipMasks']) > 0) {
+ $connector->setAllowedIpMasks($this->options['ipMasks']);
+ }
+ if (null !== $this->options['headersLimit'] && $this->options['headersLimit'] > 0) {
+ $connector->setHeadersLimit($this->options['headersLimit']);
+ }
+ if ($this->options['detectDumpTraceAndSource']) {
+ $connector->getDebugDispatcher()->detectTraceAndSource = true;
+ }
+ $dumper = $connector->getDumper();
+ $dumper->levelLimit = $this->options['dumperLevelLimit'];
+ $dumper->itemsCountLimit = $this->options['dumperItemsCountLimit'];
+ $dumper->itemSizeLimit = $this->options['dumperItemSizeLimit'];
+ $dumper->dumpSizeLimit = $this->options['dumperDumpSizeLimit'];
+ $dumper->detectCallbacks = $this->options['dumperDetectCallbacks'];
+ if ($this->options['enableEvalListener']) {
+ $connector->startEvalRequestsListener();
+ }
+ }
+
+ return $connector;
+ }
+
+ public function getConnector(): Connector
+ {
+ return $this->connector;
+ }
+
+ /**
+ * @return array
+ */
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ public function handle(LogRecord $record): bool
+ {
+ if ($this->options['enabled'] && $this->connector->isActiveClient()) {
+ return parent::handle($record);
+ }
+
+ return !$this->bubble;
+ }
+
+ /**
+ * Writes the record down to the log of the implementing handler
+ */
+ protected function write(LogRecord $record): void
+ {
+ if ($record->level->isLowerThan(Level::Notice)) {
+ $this->handleDebugRecord($record);
+ } elseif (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) {
+ $this->handleExceptionRecord($record);
+ } else {
+ $this->handleErrorRecord($record);
+ }
+ }
+
+ private function handleDebugRecord(LogRecord $record): void
+ {
+ [$tags, $filteredContext] = $this->getRecordTags($record);
+ $message = $record->message;
+ if (\count($filteredContext) > 0) {
+ $message .= ' ' . Utils::jsonEncode($this->connector->getDumper()->dump(array_filter($filteredContext)), null, true);
+ }
+ $this->connector->getDebugDispatcher()->dispatchDebug($message, $tags, $this->options['classesPartialsTraceIgnore']);
+ }
+
+ private function handleExceptionRecord(LogRecord $record): void
+ {
+ $this->connector->getErrorsDispatcher()->dispatchException($record->context['exception']);
+ }
+
+ private function handleErrorRecord(LogRecord $record): void
+ {
+ $context = $record->context;
+
+ $this->connector->getErrorsDispatcher()->dispatchError(
+ $context['code'] ?? null,
+ $context['message'] ?? $record->message,
+ $context['file'] ?? null,
+ $context['line'] ?? null,
+ $this->options['classesPartialsTraceIgnore']
+ );
+ }
+
+ /**
+ * @return array{string, mixed[]}
+ */
+ private function getRecordTags(LogRecord $record): array
+ {
+ $tags = null;
+ $filteredContext = [];
+ if ($record->context !== []) {
+ $filteredContext = $record->context;
+ foreach ($this->options['debugTagsKeysInContext'] as $key) {
+ if (isset($filteredContext[$key])) {
+ $tags = $filteredContext[$key];
+ if ($key === 0) {
+ array_shift($filteredContext);
+ } else {
+ unset($filteredContext[$key]);
+ }
+ break;
+ }
+ }
+ }
+
+ return [$tags ?? $record->level->toPsrLogLevel(), $filteredContext];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter('%message%');
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php
new file mode 100644
index 0000000..2855a9f
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessHandler.php
@@ -0,0 +1,191 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\LogRecord;
+
+/**
+ * Stores to STDIN of any process, specified by a command.
+ *
+ * Usage example:
+ *
+ * $log = new Logger('myLogger');
+ * $log->pushHandler(new ProcessHandler('/usr/bin/php /var/www/monolog/someScript.php'));
+ *
+ *
+ * @author Kolja Zuelsdorf
+ */
+class ProcessHandler extends AbstractProcessingHandler
+{
+ /**
+ * Holds the process to receive data on its STDIN.
+ *
+ * @var resource|bool|null
+ */
+ private $process;
+
+ private string $command;
+
+ private ?string $cwd;
+
+ /**
+ * @var resource[]
+ */
+ private array $pipes = [];
+
+ private float $timeout;
+
+ /**
+ * @var array>
+ */
+ protected const DESCRIPTOR_SPEC = [
+ 0 => ['pipe', 'r'], // STDIN is a pipe that the child will read from
+ 1 => ['pipe', 'w'], // STDOUT is a pipe that the child will write to
+ 2 => ['pipe', 'w'], // STDERR is a pipe to catch the any errors
+ ];
+
+ /**
+ * @param string $command Command for the process to start. Absolute paths are recommended,
+ * especially if you do not use the $cwd parameter.
+ * @param string|null $cwd "Current working directory" (CWD) for the process to be executed in.
+ * @param float $timeout The maximum timeout (in seconds) for the stream_select() function.
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(string $command, int|string|Level $level = Level::Debug, bool $bubble = true, ?string $cwd = null, float $timeout = 1.0)
+ {
+ if ($command === '') {
+ throw new \InvalidArgumentException('The command argument must be a non-empty string.');
+ }
+ if ($cwd === '') {
+ throw new \InvalidArgumentException('The optional CWD argument must be a non-empty string or null.');
+ }
+
+ parent::__construct($level, $bubble);
+
+ $this->command = $command;
+ $this->cwd = $cwd;
+ $this->timeout = $timeout;
+ }
+
+ /**
+ * Writes the record down to the log of the implementing handler
+ *
+ * @throws \UnexpectedValueException
+ */
+ protected function write(LogRecord $record): void
+ {
+ $this->ensureProcessIsStarted();
+
+ $this->writeProcessInput($record->formatted);
+
+ $errors = $this->readProcessErrors();
+ if ($errors !== '') {
+ throw new \UnexpectedValueException(sprintf('Errors while writing to process: %s', $errors));
+ }
+ }
+
+ /**
+ * Makes sure that the process is actually started, and if not, starts it,
+ * assigns the stream pipes, and handles startup errors, if any.
+ */
+ private function ensureProcessIsStarted(): void
+ {
+ if (\is_resource($this->process) === false) {
+ $this->startProcess();
+
+ $this->handleStartupErrors();
+ }
+ }
+
+ /**
+ * Starts the actual process and sets all streams to non-blocking.
+ */
+ private function startProcess(): void
+ {
+ $this->process = proc_open($this->command, static::DESCRIPTOR_SPEC, $this->pipes, $this->cwd);
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, false);
+ }
+ }
+
+ /**
+ * Selects the STDERR stream, handles upcoming startup errors, and throws an exception, if any.
+ *
+ * @throws \UnexpectedValueException
+ */
+ private function handleStartupErrors(): void
+ {
+ $selected = $this->selectErrorStream();
+ if (false === $selected) {
+ throw new \UnexpectedValueException('Something went wrong while selecting a stream.');
+ }
+
+ $errors = $this->readProcessErrors();
+
+ if (\is_resource($this->process) === false || $errors !== '') {
+ throw new \UnexpectedValueException(
+ sprintf('The process "%s" could not be opened: ' . $errors, $this->command)
+ );
+ }
+ }
+
+ /**
+ * Selects the STDERR stream.
+ *
+ * @return int|bool
+ */
+ protected function selectErrorStream()
+ {
+ $empty = [];
+ $errorPipes = [$this->pipes[2]];
+
+ $seconds = (int) $this->timeout;
+ return stream_select($errorPipes, $empty, $empty, $seconds, (int) (($this->timeout - $seconds) * 1000000));
+ }
+
+ /**
+ * Reads the errors of the process, if there are any.
+ *
+ * @codeCoverageIgnore
+ * @return string Empty string if there are no errors.
+ */
+ protected function readProcessErrors(): string
+ {
+ return (string) stream_get_contents($this->pipes[2]);
+ }
+
+ /**
+ * Writes to the input stream of the opened process.
+ *
+ * @codeCoverageIgnore
+ */
+ protected function writeProcessInput(string $string): void
+ {
+ fwrite($this->pipes[0], $string);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function close(): void
+ {
+ if (\is_resource($this->process)) {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ proc_close($this->process);
+ $this->process = null;
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php
new file mode 100644
index 0000000..9fb290f
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerInterface.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Processor\ProcessorInterface;
+use Monolog\LogRecord;
+
+/**
+ * Interface to describe loggers that have processors
+ *
+ * @author Jordi Boggiano
+ */
+interface ProcessableHandlerInterface
+{
+ /**
+ * Adds a processor in the stack.
+ *
+ * @phpstan-param ProcessorInterface|(callable(LogRecord): LogRecord) $callback
+ *
+ * @param ProcessorInterface|callable $callback
+ * @return HandlerInterface self
+ */
+ public function pushProcessor(callable $callback): HandlerInterface;
+
+ /**
+ * Removes the processor on top of the stack and returns it.
+ *
+ * @phpstan-return ProcessorInterface|(callable(LogRecord): LogRecord) $callback
+ *
+ * @throws \LogicException In case the processor stack is empty
+ * @return callable|ProcessorInterface
+ */
+ public function popProcessor(): callable;
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php
new file mode 100644
index 0000000..74eeddd
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/ProcessableHandlerTrait.php
@@ -0,0 +1,70 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\ResettableInterface;
+use Monolog\Processor\ProcessorInterface;
+use Monolog\LogRecord;
+
+/**
+ * Helper trait for implementing ProcessableInterface
+ *
+ * @author Jordi Boggiano
+ */
+trait ProcessableHandlerTrait
+{
+ /**
+ * @var callable[]
+ * @phpstan-var array<(callable(LogRecord): LogRecord)|ProcessorInterface>
+ */
+ protected array $processors = [];
+
+ /**
+ * @inheritDoc
+ */
+ public function pushProcessor(callable $callback): HandlerInterface
+ {
+ array_unshift($this->processors, $callback);
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function popProcessor(): callable
+ {
+ if (\count($this->processors) === 0) {
+ throw new \LogicException('You tried to pop from an empty processor stack.');
+ }
+
+ return array_shift($this->processors);
+ }
+
+ protected function processRecord(LogRecord $record): LogRecord
+ {
+ foreach ($this->processors as $processor) {
+ $record = $processor($record);
+ }
+
+ return $record;
+ }
+
+ protected function resetProcessors(): void
+ {
+ foreach ($this->processors as $processor) {
+ if ($processor instanceof ResettableInterface) {
+ $processor->reset();
+ }
+ }
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php
new file mode 100644
index 0000000..100e8e4
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/PsrHandler.php
@@ -0,0 +1,92 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Psr\Log\LoggerInterface;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\LogRecord;
+
+/**
+ * Proxies log messages to an existing PSR-3 compliant logger.
+ *
+ * If a formatter is configured, the formatter's output MUST be a string and the
+ * formatted message will be fed to the wrapped PSR logger instead of the original
+ * log record's message.
+ *
+ * @author Michael Moussa
+ */
+class PsrHandler extends AbstractHandler implements FormattableHandlerInterface
+{
+ /**
+ * PSR-3 compliant logger
+ */
+ protected LoggerInterface $logger;
+
+ protected FormatterInterface|null $formatter = null;
+ private bool $includeExtra;
+
+ /**
+ * @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
+ */
+ public function __construct(LoggerInterface $logger, int|string|Level $level = Level::Debug, bool $bubble = true, bool $includeExtra = false)
+ {
+ parent::__construct($level, $bubble);
+
+ $this->logger = $logger;
+ $this->includeExtra = $includeExtra;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function handle(LogRecord $record): bool
+ {
+ if (!$this->isHandling($record)) {
+ return false;
+ }
+
+ $message = $this->formatter !== null
+ ? (string) $this->formatter->format($record)
+ : $record->message;
+
+ $context = $this->includeExtra
+ ? [...$record->extra, ...$record->context]
+ : $record->context;
+
+ $this->logger->log($record->level->toPsrLogLevel(), $message, $context);
+
+ return false === $this->bubble;
+ }
+
+ /**
+ * Sets the formatter.
+ */
+ public function setFormatter(FormatterInterface $formatter): HandlerInterface
+ {
+ $this->formatter = $formatter;
+
+ return $this;
+ }
+
+ /**
+ * Gets the formatter.
+ */
+ public function getFormatter(): FormatterInterface
+ {
+ if ($this->formatter === null) {
+ throw new \LogicException('No formatter has been set and this handler does not have a default formatter');
+ }
+
+ return $this->formatter;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php
new file mode 100644
index 0000000..b2a7895
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php
@@ -0,0 +1,246 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Level;
+use Monolog\Logger;
+use Monolog\Utils;
+use Psr\Log\LogLevel;
+use Monolog\LogRecord;
+
+/**
+ * Sends notifications through the pushover api to mobile phones
+ *
+ * @author Sebastian Göttschkes
+ * @see https://www.pushover.net/api
+ */
+class PushoverHandler extends SocketHandler
+{
+ private string $token;
+
+ /** @var array */
+ private array $users;
+
+ private string $title;
+
+ private string|int|null $user = null;
+
+ private int $retry;
+
+ private int $expire;
+
+ private Level $highPriorityLevel;
+
+ private Level $emergencyLevel;
+
+ private bool $useFormattedMessage = false;
+
+ /**
+ * All parameters that can be sent to Pushover
+ * @see https://pushover.net/api
+ * @var array
+ */
+ private array $parameterNames = [
+ 'token' => true,
+ 'user' => true,
+ 'message' => true,
+ 'device' => true,
+ 'title' => true,
+ 'url' => true,
+ 'url_title' => true,
+ 'priority' => true,
+ 'timestamp' => true,
+ 'sound' => true,
+ 'retry' => true,
+ 'expire' => true,
+ 'callback' => true,
+ ];
+
+ /**
+ * Sounds the api supports by default
+ * @see https://pushover.net/api#sounds
+ * @var string[]
+ */
+ private array $sounds = [
+ 'pushover', 'bike', 'bugle', 'cashregister', 'classical', 'cosmic', 'falling', 'gamelan', 'incoming',
+ 'intermission', 'magic', 'mechanical', 'pianobar', 'siren', 'spacealarm', 'tugboat', 'alien', 'climb',
+ 'persistent', 'echo', 'updown', 'none',
+ ];
+
+ /**
+ * @param string $token Pushover api token
+ * @param string|array $users Pushover user id or array of ids the message will be sent to
+ * @param string|null $title Title sent to the Pushover API
+ * @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
+ * the pushover.net app owner. OpenSSL is required for this option.
+ * @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will
+ * send the same notification to the user.
+ * @param int $expire The expire parameter specifies how many seconds your notification will continue
+ * to be retried for (every retry seconds).
+ *
+ * @param int|string|Level|LogLevel::* $highPriorityLevel The minimum logging level at which this handler will start
+ * sending "high priority" requests to the Pushover API
+ * @param int|string|Level|LogLevel::* $emergencyLevel The minimum logging level at which this handler will start
+ * sending "emergency" requests to the Pushover API
+ *
+ *
+ * @phpstan-param string|array $users
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $highPriorityLevel
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $emergencyLevel
+ */
+ public function __construct(
+ string $token,
+ $users,
+ ?string $title = null,
+ int|string|Level $level = Level::Critical,
+ bool $bubble = true,
+ bool $useSSL = true,
+ int|string|Level $highPriorityLevel = Level::Critical,
+ int|string|Level $emergencyLevel = Level::Emergency,
+ int $retry = 30,
+ int $expire = 25200,
+ bool $persistent = false,
+ float $timeout = 0.0,
+ float $writingTimeout = 10.0,
+ ?float $connectionTimeout = null,
+ ?int $chunkSize = null
+ ) {
+ $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';
+ parent::__construct(
+ $connectionString,
+ $level,
+ $bubble,
+ $persistent,
+ $timeout,
+ $writingTimeout,
+ $connectionTimeout,
+ $chunkSize
+ );
+
+ $this->token = $token;
+ $this->users = (array) $users;
+ $this->title = $title ?? (string) gethostname();
+ $this->highPriorityLevel = Logger::toMonologLevel($highPriorityLevel);
+ $this->emergencyLevel = Logger::toMonologLevel($emergencyLevel);
+ $this->retry = $retry;
+ $this->expire = $expire;
+ }
+
+ protected function generateDataStream(LogRecord $record): string
+ {
+ $content = $this->buildContent($record);
+
+ return $this->buildHeader($content) . $content;
+ }
+
+ private function buildContent(LogRecord $record): string
+ {
+ // Pushover has a limit of 512 characters on title and message combined.
+ $maxMessageLength = 512 - \strlen($this->title);
+
+ $message = ($this->useFormattedMessage) ? $record->formatted : $record->message;
+ $message = Utils::substr($message, 0, $maxMessageLength);
+
+ $timestamp = $record->datetime->getTimestamp();
+
+ $dataArray = [
+ 'token' => $this->token,
+ 'user' => $this->user,
+ 'message' => $message,
+ 'title' => $this->title,
+ 'timestamp' => $timestamp,
+ ];
+
+ if ($record->level->value >= $this->emergencyLevel->value) {
+ $dataArray['priority'] = 2;
+ $dataArray['retry'] = $this->retry;
+ $dataArray['expire'] = $this->expire;
+ } elseif ($record->level->value >= $this->highPriorityLevel->value) {
+ $dataArray['priority'] = 1;
+ }
+
+ // First determine the available parameters
+ $context = array_intersect_key($record->context, $this->parameterNames);
+ $extra = array_intersect_key($record->extra, $this->parameterNames);
+
+ // Least important info should be merged with subsequent info
+ $dataArray = array_merge($extra, $context, $dataArray);
+
+ // Only pass sounds that are supported by the API
+ if (isset($dataArray['sound']) && !\in_array($dataArray['sound'], $this->sounds, true)) {
+ unset($dataArray['sound']);
+ }
+
+ return http_build_query($dataArray);
+ }
+
+ private function buildHeader(string $content): string
+ {
+ $header = "POST /1/messages.json HTTP/1.1\r\n";
+ $header .= "Host: api.pushover.net\r\n";
+ $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
+ $header .= "Content-Length: " . \strlen($content) . "\r\n";
+ $header .= "\r\n";
+
+ return $header;
+ }
+
+ protected function write(LogRecord $record): void
+ {
+ foreach ($this->users as $user) {
+ $this->user = $user;
+
+ parent::write($record);
+ $this->closeSocket();
+ }
+
+ $this->user = null;
+ }
+
+ /**
+ * @param int|string|Level|LogLevel::* $level
+ * @return $this
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $level
+ */
+ public function setHighPriorityLevel(int|string|Level $level): self
+ {
+ $this->highPriorityLevel = Logger::toMonologLevel($level);
+
+ return $this;
+ }
+
+ /**
+ * @param int|string|Level|LogLevel::* $level
+ * @return $this
+ *
+ * @phpstan-param value-of|value-of|Level|LogLevel::* $level
+ */
+ public function setEmergencyLevel(int|string|Level $level): self
+ {
+ $this->emergencyLevel = Logger::toMonologLevel($level);
+
+ return $this;
+ }
+
+ /**
+ * Use the formatted message?
+ *
+ * @return $this
+ */
+ public function useFormattedMessage(bool $useFormattedMessage): self
+ {
+ $this->useFormattedMessage = $useFormattedMessage;
+
+ return $this;
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php
new file mode 100644
index 0000000..c40d97c
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Level;
+use Monolog\LogRecord;
+use Predis\Client as Predis;
+use Redis;
+
+/**
+ * Logs to a Redis key using rpush
+ *
+ * usage example:
+ *
+ * $log = new Logger('application');
+ * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs");
+ * $log->pushHandler($redis);
+ *
+ * @author Thomas Tourlourat
+ */
+class RedisHandler extends AbstractProcessingHandler
+{
+ /** @var Predis|Redis */
+ private Predis|Redis $redisClient;
+ private string $redisKey;
+ protected int $capSize;
+
+ /**
+ * @param Predis|Redis $redis The redis instance
+ * @param string $key The key name to push records to
+ * @param int $capSize Number of entries to limit list size to, 0 = unlimited
+ */
+ public function __construct(Predis|Redis $redis, string $key, int|string|Level $level = Level::Debug, bool $bubble = true, int $capSize = 0)
+ {
+ $this->redisClient = $redis;
+ $this->redisKey = $key;
+ $this->capSize = $capSize;
+
+ parent::__construct($level, $bubble);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function write(LogRecord $record): void
+ {
+ if ($this->capSize > 0) {
+ $this->writeCapped($record);
+ } else {
+ $this->redisClient->rpush($this->redisKey, $record->formatted);
+ }
+ }
+
+ /**
+ * Write and cap the collection
+ * Writes the record to the redis list and caps its
+ */
+ protected function writeCapped(LogRecord $record): void
+ {
+ if ($this->redisClient instanceof Redis) {
+ $mode = \defined('Redis::MULTI') ? Redis::MULTI : 1;
+ $this->redisClient->multi($mode)
+ ->rPush($this->redisKey, $record->formatted)
+ ->ltrim($this->redisKey, -$this->capSize, -1)
+ ->exec();
+ } else {
+ $redisKey = $this->redisKey;
+ $capSize = $this->capSize;
+ $this->redisClient->transaction(function ($tx) use ($record, $redisKey, $capSize) {
+ $tx->rpush($redisKey, $record->formatted);
+ $tx->ltrim($redisKey, -$capSize, -1);
+ });
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultFormatter(): FormatterInterface
+ {
+ return new LineFormatter();
+ }
+}
diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php
new file mode 100644
index 0000000..fa8e9e9
--- /dev/null
+++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisPubSubHandler.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Formatter\FormatterInterface;
+use Monolog\Level;
+use Monolog\LogRecord;
+use Predis\Client as Predis;
+use Redis;
+
+/**
+ * Sends the message to a Redis Pub/Sub channel using PUBLISH
+ *
+ * usage example:
+ *
+ * $log = new Logger('application');
+ * $redis = new RedisPubSubHandler(new Predis\Client("tcp://localhost:6379"), "logs", Level::Warning);
+ * $log->pushHandler($redis);
+ *
+ * @author Gaëtan Faugère
+ */
+class RedisPubSubHandler extends AbstractProcessingHandler
+{
+ /** @var Predis