Updated to router v3.

This commit is contained in:
flash 2025-03-20 19:50:36 +00:00
parent d98703ebbe
commit e38c3fca85
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
10 changed files with 669 additions and 360 deletions

View file

@ -1,7 +1,7 @@
{
"require": {
"flashwave/index": "^0.2501",
"flashii/apii": "^0.3",
"flashwave/index": "^0.2503",
"flashii/apii": "^0.4",
"ramsey/uuid": "^4.7",
"sentry/sdk": "^4.0",
"nesbot/carbon": "^3.8"

574
composer.lock generated
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1915d5b83915a823d3d8b39f9b9e06b1",
"content-hash": "d44aca0929111efbc72a8ad988f8d9ea",
"packages": [
{
"name": "brick/math",
"version": "0.12.1",
"version": "0.12.3",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
"reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
"shasum": ""
},
"require": {
@ -26,7 +26,7 @@
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "5.16.0"
"vimeo/psalm": "6.8.8"
},
"type": "library",
"autoload": {
@ -56,7 +56,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.1"
"source": "https://github.com/brick/math/tree/0.12.3"
},
"funding": [
{
@ -64,7 +64,7 @@
"type": "github"
}
],
"time": "2023-11-29T23:19:16+00:00"
"time": "2025-02-28T13:11:00+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
@ -137,18 +137,20 @@
},
{
"name": "flashii/apii",
"version": "v0.3.0",
"version": "v0.4.0",
"source": {
"type": "git",
"url": "https://patchii.net/flashii/apii-php.git",
"reference": "2d6c135faddd359341762afcb9c429e279d87059"
"reference": "d330e0792515dbae2832bd8e2ac761cc685175e9"
},
"require": {
"php": ">=8.1"
"guzzlehttp/guzzle": "~7.9",
"php": ">=8.1",
"psr/http-client": "^1.0"
},
"require-dev": {
"phpstan/phpstan": "^1.12",
"phpunit/phpunit": "^10.5"
"phpstan/phpstan": "~2.1",
"phpunit/phpunit": "~10.5"
},
"type": "library",
"autoload": {
@ -170,25 +172,27 @@
],
"description": "Client library for the Flashii.net API.",
"homepage": "https://api.flashii.net",
"time": "2024-11-22T21:36:01+00:00"
"time": "2025-03-20T17:05:52+00:00"
},
{
"name": "flashwave/index",
"version": "v0.2501.221237",
"version": "v0.2503.201929",
"source": {
"type": "git",
"url": "https://patchii.net/flash/index.git",
"reference": "fee9a65e3bca341be7401fe2e21b795630f15f2a"
"reference": "1ba9e8fa34fbd30c84c23b2a9b6beb2152ca54f0"
},
"require": {
"ext-mbstring": "*",
"php": ">=8.4",
"twig/html-extra": "^3.18",
"twig/twig": "^3.18"
"psr/http-message": "^2.0",
"psr/http-server-handler": "^1.0",
"twig/html-extra": "^3.20",
"twig/twig": "^3.20"
},
"require-dev": {
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^11.5"
"phpunit/phpunit": "^12.0"
},
"suggest": {
"ext-memcache": "Support for the Index\\Cache\\Memcached namespace (only if you can't use ext-memcached for some reason).",
@ -225,7 +229,216 @@
],
"description": "Composer package for the common library for my projects.",
"homepage": "https://railgun.sh/index",
"time": "2025-01-22T12:38:11+00:00"
"time": "2025-03-20T19:29:51+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.9.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "d281ed313b989f213357e3be1a179f02196ac99b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
"guzzlehttp/psr7": "^2.7.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0",
"symfony/deprecation-contracts": "^2.2 || ^3.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"guzzle/client-integration-tests": "3.0.2",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
"psr/log": "^1.1 || ^2.0 || ^3.0"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "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": "Jeremy Lindblom",
"email": "jeremeamia@gmail.com",
"homepage": "https://github.com/jeremeamia"
},
{
"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"
}
],
"description": "Guzzle is a PHP HTTP client library",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.2"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
"type": "tidelift"
}
],
"time": "2024-07-24T11:22:20+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "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": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/2.0.4"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
"type": "tidelift"
}
],
"time": "2024-10-17T10:06:22+00:00"
},
{
"name": "guzzlehttp/psr7",
@ -345,16 +558,16 @@
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.0",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10"
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
@ -364,8 +577,9 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
@ -398,22 +612,22 @@
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0"
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2024-11-18T16:19:46+00:00"
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "nesbot/carbon",
"version": "3.8.4",
"version": "3.8.6",
"source": {
"type": "git",
"url": "https://github.com/CarbonPHP/carbon.git",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58"
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58",
"reference": "129700ed449b1f02d70272d2ac802357c8c30c58",
"url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"reference": "ff2f20cf83bd4d503720632ce8a426dc747bf7fd",
"shasum": ""
},
"require": {
@ -489,8 +703,8 @@
],
"support": {
"docs": "https://carbon.nesbot.com/docs",
"issues": "https://github.com/briannesbitt/Carbon/issues",
"source": "https://github.com/briannesbitt/Carbon"
"issues": "https://github.com/CarbonPHP/carbon/issues",
"source": "https://github.com/CarbonPHP/carbon"
},
"funding": [
{
@ -506,7 +720,7 @@
"type": "tidelift"
}
],
"time": "2024-12-27T09:25:35+00:00"
"time": "2025-02-20T17:33:38+00:00"
},
{
"name": "psr/clock",
@ -556,6 +770,58 @@
},
"time": "2022-11-25T14:36:26+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client"
},
"time": "2023-09-23T14:17:50+00:00"
},
{
"name": "psr/http-factory",
"version": "1.1.0",
@ -664,6 +930,62 @@
},
"time": "2023-04-04T09:54:51+00:00"
},
{
"name": "psr/http-server-handler",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-server-handler.git",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "^1.0 || ^2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Server\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP server-side request handler",
"keywords": [
"handler",
"http",
"http-interop",
"psr",
"psr-15",
"psr-7",
"request",
"response",
"server"
],
"support": {
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
},
"time": "2023-04-10T20:06:20+00:00"
},
{
"name": "psr/log",
"version": "3.0.2",
@ -760,16 +1082,16 @@
},
{
"name": "ramsey/collection",
"version": "2.0.0",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/ramsey/collection.git",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109",
"shasum": ""
},
"require": {
@ -777,25 +1099,22 @@
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"ergebnis/composer-normalize": "^2.45",
"fakerphp/faker": "^1.24",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"jangregor/phpstan-prophecy": "^2.1",
"mockery/mockery": "^1.6",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpspec/prophecy-phpunit": "^2.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-mockery": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^10.5",
"ramsey/coding-standard": "^2.3",
"ramsey/conventional-commits": "^1.6",
"roave/security-advisories": "dev-latest"
},
"type": "library",
"extra": {
@ -833,19 +1152,9 @@
],
"support": {
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.0.0"
"source": "https://github.com/ramsey/collection/tree/2.1.0"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2022-12-31T21:50:55+00:00"
"time": "2025-03-02T04:48:29+00:00"
},
{
"name": "ramsey/uuid",
@ -1226,16 +1535,16 @@
},
{
"name": "symfony/mime",
"version": "v7.2.1",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283"
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283",
"reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283",
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
"shasum": ""
},
"require": {
@ -1290,7 +1599,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v7.2.1"
"source": "https://github.com/symfony/mime/tree/v7.2.4"
},
"funding": [
{
@ -1306,7 +1615,7 @@
"type": "tidelift"
}
],
"time": "2024-12-07T08:50:44+00:00"
"time": "2025-02-19T08:51:20+00:00"
},
{
"name": "symfony/options-resolver",
@ -1698,82 +2007,6 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"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\\Php81\\": ""
},
"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.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.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-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php83",
"version": "v1.31.0",
@ -1852,16 +2085,16 @@
},
{
"name": "symfony/translation",
"version": "v7.2.2",
"version": "v7.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923"
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923",
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923",
"url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a",
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a",
"shasum": ""
},
"require": {
@ -1927,7 +2160,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v7.2.2"
"source": "https://github.com/symfony/translation/tree/v7.2.4"
},
"funding": [
{
@ -1943,7 +2176,7 @@
"type": "tidelift"
}
],
"time": "2024-12-07T08:18:10+00:00"
"time": "2025-02-13T10:27:23+00:00"
},
{
"name": "symfony/translation-contracts",
@ -2025,20 +2258,20 @@
},
{
"name": "twig/html-extra",
"version": "v3.18.0",
"version": "v3.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/html-extra.git",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a"
"reference": "f7d54d4de1b64182af745cfb66777f699b599734"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/c63b28e192c1b7c15bb60f81d2e48b140846239a",
"reference": "c63b28e192c1b7c15bb60f81d2e48b140846239a",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/f7d54d4de1b64182af745cfb66777f699b599734",
"reference": "f7d54d4de1b64182af745cfb66777f699b599734",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/mime": "^5.4|^6.4|^7.0",
"twig/twig": "^3.13|^4.0"
@ -2077,7 +2310,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.18.0"
"source": "https://github.com/twigphp/html-extra/tree/v3.20.0"
},
"funding": [
{
@ -2089,28 +2322,27 @@
"type": "tidelift"
}
],
"time": "2024-12-29T10:29:59+00:00"
"time": "2025-01-31T20:45:36+00:00"
},
{
"name": "twig/twig",
"version": "v3.18.0",
"version": "v3.20.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50"
"reference": "3468920399451a384bef53cf7996965f7cd40183"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"reference": "acffa88cc2b40dbe42eaf3a5025d6c0d4600cc50",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/3468920399451a384bef53cf7996965f7cd40183",
"reference": "3468920399451a384bef53cf7996965f7cd40183",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
@ -2157,7 +2389,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.18.0"
"source": "https://github.com/twigphp/Twig/tree/v3.20.0"
},
"funding": [
{
@ -2169,7 +2401,7 @@
"type": "tidelift"
}
],
"time": "2024-12-29T10:51:50+00:00"
"time": "2025-02-13T08:34:43+00:00"
}
],
"packages-dev": [],

View file

@ -5,8 +5,11 @@ use InvalidArgumentException;
use RuntimeException;
use Flashii\V1\Users\V1User;
use Index\CsrfToken;
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\FormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\ExactRoute;
use Index\Templating\TplEnvironment;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Ramsey\Uuid\Uuid;
@ -22,20 +25,6 @@ class ClientsRoutes implements RouteHandler, UrlSource {
private ?V1User $authInfo
) {}
#[HttpMiddleware('/clients')]
public function verifyRequest(HttpResponseBuilder $response, HttpRequest $request) {
if($this->authInfo === null)
return 403;
if($request->method === 'POST') {
if(!($request->content instanceof FormHttpContent))
return 400;
if(!$request->content->hasParam('csrfp') || !$this->csrfp->verifyToken((string)$request->content->getParam('csrfp')))
return 403;
}
}
private const CLIENTS_ERRORS = [
'link' => [
'already' => 'You already have a linked Minecraft username, unlink the other one first.',
@ -44,9 +33,10 @@ class ClientsRoutes implements RouteHandler, UrlSource {
],
];
#[HttpGet('/clients')]
#[ExactRoute('GET', '/clients')]
#[UrlFormat('clients:index', '/clients', ['error' => '<error>'])]
public function getClients(HttpResponseBuilder $response, HttpRequest $request) {
#[Before('mince:require-authed')]
public function getClients(HttpRequest $request) {
$template = $this->templating->load('clients/index');
$errorCode = (string)$request->getParam('error');
@ -77,15 +67,18 @@ class ClientsRoutes implements RouteHandler, UrlSource {
return $template->render();
}
#[HttpPost('/clients/link')]
#[ExactRoute('POST', '/clients/link')]
#[UrlFormat('clients:link', '/clients/link')]
public function postLink(HttpResponseBuilder $response, HttpRequest $request) {
#[Before('mince:require-authed')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postLink(HttpResponseBuilder $response, FormContent $content) {
if($this->clientsCtx->accountLinks->checkHasLink($this->authInfo->getId())) {
$response->redirect($this->urls->format('clients:index', ['error' => 'link:already']));
return;
}
$code = (string)$request->content->getParam('code');
$code = (string)$content->getParam('code');
if(strlen($code) !== 10) {
$response->redirect($this->urls->format('clients:index', ['error' => 'link:format']));
return;
@ -107,17 +100,23 @@ class ClientsRoutes implements RouteHandler, UrlSource {
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/unlink')]
#[ExactRoute('POST', '/clients/unlink')]
#[UrlFormat('clients:unlink', '/clients/unlink')]
#[Before('mince:require-authed')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postUnlink(HttpResponseBuilder $response) {
$this->clientsCtx->accountLinks->deleteLink(userInfo: $this->authInfo->getId());
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/authorise')]
#[ExactRoute('POST', '/clients/authorise')]
#[UrlFormat('clients:authorise', '/clients/authorise')]
public function postAuthorise(HttpResponseBuilder $response, HttpRequest $request) {
$authId = (string)$request->content->getParam('auth');
#[Before('mince:require-authed')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postAuthorise(HttpResponseBuilder $response, FormContent $content) {
$authId = (string)$content->getParam('auth');
if(empty($authId))
return 404;
@ -143,10 +142,13 @@ class ClientsRoutes implements RouteHandler, UrlSource {
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/deauthorise')]
#[ExactRoute('POST', '/clients/deauthorise')]
#[UrlFormat('clients:deauthorise', '/clients/deauthorise')]
public function postDeauthorise(HttpResponseBuilder $response, HttpRequest $request) {
$authId = (string)$request->content->getParam('auth');
#[Before('mince:require-authed')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postDeauthorise(HttpResponseBuilder $response, FormContent $content) {
$authId = (string)$content->getParam('auth');
if(empty($authId))
return 404;

View file

@ -3,7 +3,8 @@ namespace Mince;
use Flashii\V1\Users\V1User;
use Index\Http\HttpResponseBuilder;
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Routes\ExactRoute;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Index\Templating\TplEnvironment;
@ -17,31 +18,31 @@ class HomeRoutes implements RouteHandler, UrlSource {
private string $loginUrl
) {}
#[HttpGet('/')]
#[ExactRoute('GET', '/')]
#[UrlFormat('index', '/')]
public function getIndex() {
return $this->templating->render('index');
}
#[HttpGet('/login')]
#[ExactRoute('GET', '/login')]
#[UrlFormat('login', '/login')]
public function getLogin(HttpResponseBuilder $response) {
$response->redirect($this->authInfo === null ? $this->loginUrl : $this->urls->format('index'));
}
#[HttpGet('/downloads')]
#[ExactRoute('GET', '/downloads')]
#[UrlFormat('downloads', '/downloads')]
public function getDownloads($response) {
$response->redirect('https://fii.moe/mc');
}
#[HttpGet('/guide')]
#[ExactRoute('GET', '/guide')]
#[UrlFormat('guide', '/guide')]
public function getGuide($response) {
$response->redirect('https://fii.moe/mcguide');
}
#[HttpGet('/index.php')]
#[ExactRoute('GET', '/index.php')]
public function getRedirect(HttpResponseBuilder $response) {
$response->redirect($this->urls->format('index'), true);
}

View file

@ -3,11 +3,15 @@ namespace Mince;
use Flashii\{FlashiiClient,FlashiiUrls};
use Flashii\Credentials\MisuzuCredentials;
use Flashii\V1\Users\V1User;
use Index\{CsrfToken,Dependencies};
use Index\Config\Config;
use Index\Db\DbConnection;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\RouteHandler;
use Index\Http\Content\FormContent;
use Index\Http\Routing\{HandlerContext,RouteHandler};
use Index\Http\Routing\Filters\FilterInfo;
use Index\Http\Routing\Processors\ProcessorInfo;
use Index\Templating\TplEnvironment;
use Index\Urls\UrlSource;
@ -40,13 +44,14 @@ class MinceContext {
$this->deps->register($routingCtx->router);
$this->deps->register($routingCtx->urls);
$routingCtx->router->use('/', function(HttpResponseBuilder $response, HttpRequest $request) use ($templating) {
$routingCtx->router->filter(FilterInfo::prefix('/', function(HttpResponseBuilder $response, HttpRequest $request) use ($templating) {
$authToken = (string)$request->getCookie('msz_auth');
if(empty($authToken)) {
$csrfSecret = $request->remoteAddress;
$userInfo = null;
} else {
$flashii = new FlashiiClient('Mince', new MisuzuCredentials($authToken), new FlashiiUrls(
$this->config->getString('apii:url', FlashiiUrls::PROD_URL),
$this->config->getString('apii:api', FlashiiUrls::PROD_API_URL)
));
@ -71,7 +76,30 @@ class MinceContext {
'user' => $userInfo,
'csrfp' => $csrfToken->createToken(),
]);
});
}));
$routingCtx->router->preprocessor(ProcessorInfo::pre('mince:require-authed', function(HandlerContext $context) {
$authInfo = $this->deps->resolve(V1User::class);
if($authInfo === null)
return 403;
$context->deps->register($authInfo);
}));
$routingCtx->router->preprocessor(ProcessorInfo::pre('mince:verify-csrf', function(
HandlerContext $context,
?FormContent $content = null
) {
if($content === null)
return 400;
$csrf = $this->deps->resolve(CsrfToken::class);
if($csrf === null)
return 500;
if(!$content->hasParam('csrfp') || !$csrf->verifyToken((string)$content->getParam('csrfp')))
return 403;
}));
$routingCtx->register($this->deps->construct(RpcRoutes::class, secretKey: $this->config->getString('rpc:secret'), clientsUrl: $this->config->getString('urls:clients')));
$routingCtx->register($this->deps->construct(HomeRoutes::class, loginUrl: $this->config->getString('site:login')));

View file

@ -4,6 +4,7 @@ namespace Mince;
use stdClass;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\Router;
use Index\Http\Routing\Routes\RouteInfo;
use Ramsey\Uuid\{Uuid,UuidInterface};
final class MojangInterop {
@ -34,8 +35,14 @@ final class MojangInterop {
}
public static function registerRoutes(Router $router): void {
$router->get('/uuid', fn($response, $request) => self::uuidResolver($response, $request));
$router->get('/blockedservers', fn($response, $request) => self::proxyBlockServers($response, $request));
$router->route(RouteInfo::exact(
'GET', '/uuid',
fn(HttpResponseBuilder $response, HttpRequest $request) => self::uuidResolver($response, $request)
));
$router->route(RouteInfo::exact(
'GET', '/blockedservers',
fn(HttpResponseBuilder $response, HttpRequest $request) => self::proxyBlockServers($response, $request)
));
}
public static function uuidResolver(HttpResponseBuilder $response, HttpRequest $request): string {

View file

@ -2,18 +2,19 @@
namespace Mince;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpRouter,Router,RouteHandler};
use Index\Http\Routing\{Router,RouteHandler};
use Index\Http\Routing\Filters\FilterInfo;
use Index\Urls\{ArrayUrlRegistry,UrlRegistry,UrlSource};
use Index\Templating\TplEnvironment;
class RoutingContext {
public private(set) UrlRegistry $urls;
public private(set) HttpRouter $router;
public private(set) Router $router;
public function __construct(TplEnvironment $templating) {
$this->urls = new ArrayUrlRegistry;
$this->router = new HttpRouter(errorHandler: new RoutingErrorHandler($templating));
$this->router->use('/', fn(HttpResponseBuilder $resp) => $resp->setPoweredBy('Mince'));
$this->router = new Router(errorHandler: new RoutingErrorHandler($templating));
$this->router->filter(FilterInfo::prefix('/', fn(HttpResponseBuilder $resp) => $resp->setPoweredBy('Mince')));
}
public function register(RouteHandler|UrlSource $handler): void {

View file

@ -1,21 +1,29 @@
<?php
namespace Mince;
use Index\Http\{HttpErrorHandler,HttpResponseBuilder,HttpRequest};
use Index\Http\HttpResponse;
use Index\Http\Streams\Stream;
use Index\Http\Routing\HandlerContext;
use Index\Http\Routing\ErrorHandling\ErrorHandler;
use Index\Templating\TplEnvironment;
class RoutingErrorHandler implements HttpErrorHandler {
class RoutingErrorHandler implements ErrorHandler {
public function __construct(
private TplEnvironment $templating
) {}
public function handle(HttpResponseBuilder $response, HttpRequest $request, int $code, string $message): void {
$response->setTypeHTML();
$response->content = $this->templating->render('http-error', [
public function handle(HandlerContext $context): void {
if(!$context->response->needsBody)
return;
$context->response->setTypeHTML('utf-8');
$context->response->body = Stream::createStream($this->templating->render('http-error', [
'error' => [
'code' => sprintf('%03d', $code),
'text' => $message,
'code' => sprintf('%03d', $context->response->statusCode),
'text' => $context->response->reasonPhrase === ''
? HttpResponse::defaultReasonPhase($context->response->statusCode)
: $context->response->reasonPhrase,
],
]);
]));
}
}

View file

@ -5,8 +5,11 @@ use stdClass;
use InvalidArgumentException;
use RuntimeException;
use Stringable;
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\UrlEncodedFormContent;
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\ExactRoute;
use Mince\Clients\ClientsContext;
use Mince\Users\UsersContext;
use Ramsey\Uuid\Uuid;
@ -44,8 +47,9 @@ class RpcRoutes implements RouteHandler {
return self::createPayload('error', $attrs);
}
#[HttpMiddleware('/rpc')]
public function verifyRequest(HttpResponseBuilder $response, HttpRequest $request) {
#[ExactRoute('POST', '/rpc/auth')]
#[Before('input:urlencoded', required: false)]
public function postAuth(HttpResponseBuilder $response, HttpRequest $request, ?UrlEncodedFormContent $content = null) {
$userTime = (int)$request->getHeaderLine('X-Mince-Time');
$userHash = base64_decode((string)$request->getHeaderLine('X-Mince-Hash'));
@ -53,28 +57,24 @@ class RpcRoutes implements RouteHandler {
if(empty($userHash) || $userTime < $currentTime - 60 || $userTime > $currentTime + 60)
return self::createErrorPayload('verification', 'Request verification failed.');
$paramString = $request->getParamString();
if($request->method === 'POST') {
if(!($request->content instanceof FormHttpContent))
return self::createErrorPayload('request', 'Request body is not in expect format.');
$bodyString = $request->content->getParamString();
if(!empty($paramString) && !empty($bodyString))
$paramString .= '&';
$paramString .= $bodyString;
}
$paramString = $request->getParamString(true);
if($content === null)
return self::createErrorPayload('request', 'Request body is not in expect format.');
$verifyText = (string)$userTime . '#' . $request->path . '?' . $paramString;
$bodyString = $content->getParamString(true);
if(!empty($paramString) && !empty($bodyString))
$paramString .= '&';
$paramString .= $bodyString;
$verifyText = (string)$userTime . '#' . $request->requestTarget . '?' . $paramString;
$verifyHash = hash_hmac('sha256', $verifyText, $this->secretKey, true);
if(!hash_equals($verifyHash, $userHash))
return self::createErrorPayload('verification', 'Request verification failed.');
}
#[HttpPost('/rpc/auth')]
public function postAuth(HttpResponseBuilder $response, HttpRequest $request) {
$id = (string)$request->content->getParam('id');
$name = (string)$request->content->getParam('name');
$addr = (string)$request->content->getParam('ip');
$id = (string)$content->getParam('id');
$name = (string)$content->getParam('name');
$addr = (string)$content->getParam('ip');
if(empty($name))
return self::createErrorPayload('auth:username', 'Username is invalid.');

View file

@ -8,9 +8,13 @@ use InvalidArgumentException;
use RuntimeException;
use Flashii\V1\Users\V1User;
use Index\{CsrfToken,XString};
use Index\Http\{FormHttpContent,HttpResponseBuilder,HttpRequest};
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerCommon};
use Index\Json\JsonHttpContent;
use Index\Http\{HttpResponseBuilder,HttpRequest};
use Index\Http\Content\{FormContent,MultipartFormContent};
use Index\Http\Content\Multipart\FileMultipartFormData;
use Index\Http\Routing\{HandlerContext,RouteHandler,RouteHandlerCommon};
use Index\Http\Routing\Processors\{Before,Preprocessor};
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
use Index\Http\Streams\Stream;
use Index\Templating\TplEnvironment;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Mince\MojangInterop;
@ -59,23 +63,14 @@ class SkinsRoutes implements RouteHandler, UrlSource {
unlink($path);
}
#[HttpMiddleware('/skins')]
public function verifyRequest(HttpResponseBuilder $response, HttpRequest $request) {
if($this->authInfo === null)
return 403;
#[Preprocessor('mince:skins:get-link')]
public function preGetLink(HandlerContext $context): void {
try {
$this->linkInfo = $this->clientsCtx->accountLinks->getLink(userInfo: $this->authInfo->getId());
} catch(RuntimeException $ex) {
$response->redirect($this->urls->format('clients:index'));
return true;
}
if($request->method === 'POST') {
if(!($request->content instanceof FormHttpContent))
return 400;
if(!$request->content->hasParam('csrfp') || !$this->csrfp->verifyToken((string)$request->content->getParam('csrfp')))
return 403;
$context->response->statusCode = 302;
$context->response->redirect($this->urls->format('clients:index'));
$context->halt();
}
}
@ -91,9 +86,11 @@ class SkinsRoutes implements RouteHandler, UrlSource {
],
];
#[HttpGet('/skins')]
#[ExactRoute('GET', '/skins')]
#[UrlFormat('skins:index', '/skins', ['error' => '<error>'])]
public function getSkins(HttpResponseBuilder $response, HttpRequest $request) {
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
public function getSkins(HttpRequest $request) {
$skinInfo = $this->skinsCtx->skins->getSkin($this->linkInfo);
$skinPath = $skinInfo === null ? null : $this->getRemotePath($skinInfo->hash, false);
$capeInfo = $this->skinsCtx->capes->getCape($this->linkInfo);
@ -125,71 +122,88 @@ class SkinsRoutes implements RouteHandler, UrlSource {
return $template->render();
}
#[HttpPost('/skins/upload-skin')]
#[ExactRoute('POST', '/skins/upload-skin')]
#[UrlFormat('skins:skin:upload', '/skins/upload-skin')]
public function postUploadSkin(HttpResponseBuilder $response, HttpRequest $request) {
if(!$request->content->hasUploadedFile('texture'))
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
#[Before('input:multipart')]
#[Before('mince:verify-csrf')]
public function postUploadSkin(HttpResponseBuilder $response, MultipartFormContent $content) {
$texture = $content->getParamData('texture');
if(!($texture instanceof FileMultipartFormData))
return 400;
$texture = $request->content->getUploadedFile('texture');
$model = (string)$request->content->getParam('model');
$model = (string)$content->getParam('model');
if(!in_array($model, SkinsData::MODELS)) {
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:model']));
return;
}
if($texture->size > 512000) {
if($texture->getSize() > 512000) {
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:size']));
return;
}
$skinInfo = $this->skinsCtx->skins->getSkin($this->linkInfo);
$tmpPath = $texture->localFileName;
$hasNewFile = is_file($tmpPath);
if($hasNewFile) {
try {
$imagick = new Imagick($tmpPath);
$imagick->setImageFormat('png');
$imagick->setBackgroundColor(new ImagickPixel('transparent'));
$imagick->setImageExtent(64, $imagick->getImageHeight() < 64 ? 32 : 64);
$imagick->stripImage();
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:format']));
return;
}
$hash = hash_file('sha256', $tmpPath);
$localPath = $this->getLocalPath($hash);
} else {
$hash = $skinInfo->hash;
$tmpPath = null;
if($texture->getClientFilename() !== '') {
$tmpPath = tempnam(sys_get_temp_dir(), 'mince-skin-');
$texture->moveTo($tmpPath);
}
try {
try {
// apply new skin
if($hasNewFile && !is_file($localPath))
$texture->moveTo($localPath);
$this->skinsCtx->skins->updateSkin($this->linkInfo, $hash, $model);
} finally {
// see about deleting the old one
if($skinInfo !== null)
$this->deleteLocalFileMaybe($skinInfo->hash);
}
} finally {
// try to delete new one if something went awry
if($hasNewFile)
$this->deleteLocalFileMaybe($hash);
}
$hasNewFile = $tmpPath !== null;
if($hasNewFile) {
try {
$imagick = new Imagick($tmpPath);
$imagick->setImageFormat('png');
$imagick->setBackgroundColor(new ImagickPixel('transparent'));
$imagick->setImageExtent(64, $imagick->getImageHeight() < 64 ? 32 : 64);
$imagick->stripImage();
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:format']));
return;
}
$response->redirect($this->urls->format('skins:index'));
$hash = hash_file('sha256', $tmpPath);
$localPath = $this->getLocalPath($hash);
} else {
$hash = $skinInfo->hash;
}
try {
try {
// apply new skin
if($hasNewFile && !is_file($localPath))
rename($tmpPath, $localPath);
$this->skinsCtx->skins->updateSkin($this->linkInfo, $hash, $model);
} finally {
// see about deleting the old one
if($skinInfo !== null)
$this->deleteLocalFileMaybe($skinInfo->hash);
}
} finally {
// try to delete new one if something went awry
if($hasNewFile)
$this->deleteLocalFileMaybe($hash);
}
$response->redirect($this->urls->format('skins:index'));
} finally {
if($tmpPath !== null && is_file($tmpPath))
unlink($tmpPath);
}
}
#[HttpPost('/skins/delete-skin')]
#[ExactRoute('POST', '/skins/delete-skin')]
#[UrlFormat('skins:skin:delete', '/skins/delete-skin')]
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postDeleteSkin(HttpResponseBuilder $response) {
$skinInfo = $this->skinsCtx->skins->getSkin($this->linkInfo);
if($skinInfo !== null) {
@ -200,60 +214,75 @@ class SkinsRoutes implements RouteHandler, UrlSource {
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/upload-cape')]
#[ExactRoute('POST', '/skins/upload-cape')]
#[UrlFormat('skins:cape:upload', '/skins/upload-cape')]
public function postUploadCape(HttpResponseBuilder $response, HttpRequest $request) {
if(!$request->content->hasUploadedFile('texture'))
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
#[Before('input:multipart')]
#[Before('mince:verify-csrf')]
public function postUploadCape(HttpResponseBuilder $response, MultipartFormContent $content) {
$texture = $content->getParamData('texture');
if(!($texture instanceof FileMultipartFormData))
return 400;
$texture = $request->content->getUploadedFile('texture');
if($texture->size > 256000) {
if($texture->getSize() > 256000) {
$response->redirect($this->urls->format('skins:index', ['error' => 'cape:size']));
return;
}
$tmpPath = $texture->localFileName;
$tmpPath = tempnam(sys_get_temp_dir(), 'mince-cape-');
try {
$imagick = new Imagick($tmpPath);
$imagick->setImageFormat('png');
$imagick->setBackgroundColor(new ImagickPixel('transparent'));
$imagick->setImageExtent(64, 32);
$imagick->stripImage();
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect($this->urls->format('skins:index', ['error' => 'cape:format']));
return;
}
$hash = hash_file('sha256', $tmpPath);
$localPath = $this->getLocalPath($hash);
try {
// get previous cape
$capeInfo = $this->skinsCtx->capes->getCape($this->linkInfo);
$texture->moveTo($tmpPath);
try {
// apply new cape
if(!is_file($localPath))
$texture->moveTo($localPath);
$this->skinsCtx->capes->updateCape($this->linkInfo, $hash);
} finally {
// see about deleting the old one
if($capeInfo !== null)
$this->deleteLocalFileMaybe($capeInfo->hash);
$imagick = new Imagick($tmpPath);
$imagick->setImageFormat('png');
$imagick->setBackgroundColor(new ImagickPixel('transparent'));
$imagick->setImageExtent(64, 32);
$imagick->stripImage();
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect($this->urls->format('skins:index', ['error' => 'cape:format']));
return;
}
} finally {
// try to delete new one if something went awry
$this->deleteLocalFileMaybe($hash);
}
$response->redirect($this->urls->format('skins:index'));
$hash = hash_file('sha256', $tmpPath);
$localPath = $this->getLocalPath($hash);
try {
// get previous cape
$capeInfo = $this->skinsCtx->capes->getCape($this->linkInfo);
try {
// apply new cape
if(!is_file($localPath))
rename($tmpPath, $localPath);
$this->skinsCtx->capes->updateCape($this->linkInfo, $hash);
} finally {
// see about deleting the old one
if($capeInfo !== null)
$this->deleteLocalFileMaybe($capeInfo->hash);
}
} finally {
// try to delete new one if something went awry
$this->deleteLocalFileMaybe($hash);
}
$response->redirect($this->urls->format('skins:index'));
} finally {
if(is_file($tmpPath))
unlink($tmpPath);
}
}
#[HttpPost('/skins/delete-cape')]
#[ExactRoute('POST', '/skins/delete-cape')]
#[UrlFormat('skins:cape:delete', '/skins/delete-cape')]
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postDeleteCape(HttpResponseBuilder $response) {
$capeInfo = $this->skinsCtx->capes->getCape($this->linkInfo);
if($capeInfo !== null) {
@ -264,12 +293,16 @@ class SkinsRoutes implements RouteHandler, UrlSource {
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/import')]
#[ExactRoute('POST', '/skins/import')]
#[UrlFormat('skins:import', '/skins/import')]
public function postImport(HttpResponseBuilder $response, HttpRequest $request) {
#[Before('mince:require-authed')]
#[Before('mince:skins:get-link')]
#[Before('input:urlencoded')]
#[Before('mince:verify-csrf')]
public function postImport(HttpResponseBuilder $response, HttpRequest $request, FormContent $content) {
$userAgent = $request->getHeaderLine('User-Agent');
$userName = (string)$request->content->getParam('username');
$userName = (string)$content->getParam('username');
if($userName === '')
$userName = $this->linkInfo->name;
@ -383,7 +416,7 @@ class SkinsRoutes implements RouteHandler, UrlSource {
return $profileInfo;
}
#[HttpGet('/session/minecraft/profile/([a-fA-F0-9\-]+)')]
#[PatternRoute('GET', '/session/minecraft/profile/([a-fA-F0-9\-]+)')]
public function getSessionMinecraftProfile(HttpResponseBuilder $response, HttpRequest $request, string $id) {
try {
$uuid = Uuid::fromString($id);
@ -409,16 +442,13 @@ class SkinsRoutes implements RouteHandler, UrlSource {
return $this->getProfileInfo($uuid, true);
}
#[HttpPost('/session/minecraft/join')]
public function postSessionMinecraftJoin(HttpResponseBuilder $response, HttpRequest $request) {
if(!($request->content instanceof JsonHttpContent))
return 415;
#[ExactRoute('POST', '/session/minecraft/join')]
public function postSessionMinecraftJoin() {
// just accept this always idk if it matters
return 204;
}
#[HttpGet('/session/minecraft/hasJoined')]
#[ExactRoute('GET', '/session/minecraft/hasJoined')]
public function getSessionMinecraftHasJoined(HttpResponseBuilder $response, HttpRequest $request) {
$userName = (string)$request->getParam('username');
$serverId = (string)$request->getParam('serverId');
@ -428,7 +458,7 @@ class SkinsRoutes implements RouteHandler, UrlSource {
return $this->getProfileInfo($uuid, false);
}
#[HttpGet('/users/profiles/minecraft/([A-Za-z0-9_]+)')]
#[PatternRoute('GET', '/users/profiles/minecraft/([A-Za-z0-9_]+)')]
public function getUsersMinecraftProfile(HttpResponseBuilder $response, HttpRequest $request, string $name) {
try {
$linkInfo = $this->clientsCtx->accountLinks->getLink(name: $name);
@ -447,8 +477,8 @@ class SkinsRoutes implements RouteHandler, UrlSource {
}
// quirky path and two of them to achieve equal string length with http://s3.amazonaws.com/MinecraftSkins/ for flashii.net and edgii.net
#[HttpGet('/s3MinecraftSkins/([A-Za-z0-9_]+).png')]
#[HttpGet('/s3s3MinecraftSkins/([A-Za-z0-9_]+).png')]
#[PatternRoute('GET', '/s3MinecraftSkins/([A-Za-z0-9_]+).png')]
#[PatternRoute('GET', '/s3s3MinecraftSkins/([A-Za-z0-9_]+).png')]
public function getS3MinecraftSkin(HttpResponseBuilder $response, HttpRequest $request, string $name) {
try {
$linkInfo = $this->clientsCtx->accountLinks->getLink(name: $name);