+
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
/vendor
|
/vendor
|
||||||
.env
|
.env
|
||||||
|
requests.sqlite3
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"clue/reactphp-sqlite": "^1.6",
|
"clue/reactphp-sqlite": "^1.6",
|
||||||
"clue/framework-x": "^0.16"
|
"clue/framework-x": "^0.16",
|
||||||
|
"vlucas/phpdotenv": "^5.6",
|
||||||
|
"react/cache": "^1.2",
|
||||||
|
"clue/mq-react": "^1.6",
|
||||||
|
"smarty/smarty": "^5.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"XBotControl\\": "src/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
603
composer.lock
generated
603
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "db1acaba19a0c8b768f5023662d59dc1",
|
"content-hash": "2ff629509c131622c46d8c67505d54b2",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "clue/framework-x",
|
"name": "clue/framework-x",
|
||||||
@@ -78,6 +78,77 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-03-05T14:41:18+00:00"
|
"time": "2024-03-05T14:41:18+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "clue/mq-react",
|
||||||
|
"version": "v1.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/clue/reactphp-mq.git",
|
||||||
|
"reference": "cab0147723017bc2deb3f248c607ad8e3c87e509"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/clue/reactphp-mq/zipball/cab0147723017bc2deb3f248c607ad8e3c87e509",
|
||||||
|
"reference": "cab0147723017bc2deb3f248c607ad8e3c87e509",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3",
|
||||||
|
"react/promise": "^3 || ^2.2.1 || ^1.2.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||||
|
"react/async": "^4 || ^3 || ^2",
|
||||||
|
"react/event-loop": "^1.2",
|
||||||
|
"react/http": "^1.8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Clue\\React\\Mq\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Christian Lück",
|
||||||
|
"email": "christian@clue.engineering"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once, built on top of ReactPHP",
|
||||||
|
"homepage": "https://github.com/clue/reactphp-mq",
|
||||||
|
"keywords": [
|
||||||
|
"Mini Queue",
|
||||||
|
"async",
|
||||||
|
"concurrency",
|
||||||
|
"job",
|
||||||
|
"message",
|
||||||
|
"message queue",
|
||||||
|
"queue",
|
||||||
|
"rate limit",
|
||||||
|
"reactphp",
|
||||||
|
"throttle",
|
||||||
|
"worker"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/clue/reactphp-mq/issues",
|
||||||
|
"source": "https://github.com/clue/reactphp-mq/tree/v1.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://clue.engineering/support",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/clue",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-07-28T14:12:19+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "clue/ndjson-react",
|
"name": "clue/ndjson-react",
|
||||||
"version": "v1.3.0",
|
"version": "v1.3.0",
|
||||||
@@ -311,6 +382,68 @@
|
|||||||
},
|
},
|
||||||
"time": "2020-11-24T22:02:12+00:00"
|
"time": "2020-11-24T22:02:12+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "graham-campbell/result-type",
|
||||||
|
"version": "v1.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||||
|
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||||
|
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"phpoption/phpoption": "^1.9.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GrahamCampbell\\ResultType\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An Implementation Of The Result Type",
|
||||||
|
"keywords": [
|
||||||
|
"Graham Campbell",
|
||||||
|
"GrahamCampbell",
|
||||||
|
"Result Type",
|
||||||
|
"Result-Type",
|
||||||
|
"result"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||||
|
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-07-20T21:45:45+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/fast-route",
|
"name": "nikic/fast-route",
|
||||||
"version": "v1.3.0",
|
"version": "v1.3.0",
|
||||||
@@ -361,6 +494,81 @@
|
|||||||
},
|
},
|
||||||
"time": "2018-02-13T20:26:39+00:00"
|
"time": "2018-02-13T20:26:39+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpoption/phpoption",
|
||||||
|
"version": "1.9.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/schmittjoh/php-option.git",
|
||||||
|
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||||
|
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||||
|
"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 || ^10.5.28"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": false
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.9-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"PhpOption\\": "src/PhpOption/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Johannes M. Schmitt",
|
||||||
|
"email": "schmittjoh@gmail.com",
|
||||||
|
"homepage": "https://github.com/schmittjoh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Option Type for PHP",
|
||||||
|
"keywords": [
|
||||||
|
"language",
|
||||||
|
"option",
|
||||||
|
"php",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||||
|
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-07-20T21:41:07+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-message",
|
"name": "psr/http-message",
|
||||||
"version": "1.1",
|
"version": "1.1",
|
||||||
@@ -1109,6 +1317,399 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-06-11T12:45:25+00:00"
|
"time": "2024-06-11T12:45:25+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "smarty/smarty",
|
||||||
|
"version": "v5.4.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/smarty-php/smarty.git",
|
||||||
|
"reference": "642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f",
|
||||||
|
"reference": "642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.27"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^8.5 || ^7.5",
|
||||||
|
"smarty/smarty-lexer": "^4.0.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Smarty\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"LGPL-3.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Monte Ohrt",
|
||||||
|
"email": "monte@ohrt.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Uwe Tews",
|
||||||
|
"email": "uwe.tews@googlemail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rodney Rehm",
|
||||||
|
"email": "rodney.rehm@medialize.de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Simon Wisselink",
|
||||||
|
"homepage": "https://www.iwink.nl/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Smarty - the compiling PHP template engine",
|
||||||
|
"homepage": "https://smarty-php.github.io/smarty/",
|
||||||
|
"keywords": [
|
||||||
|
"templating"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://github.com/smarty-php/smarty/discussions",
|
||||||
|
"issues": "https://github.com/smarty-php/smarty/issues",
|
||||||
|
"source": "https://github.com/smarty-php/smarty/tree/v5.4.2"
|
||||||
|
},
|
||||||
|
"time": "2024-11-20T21:18:16+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-ctype",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
|
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||||
|
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-ctype": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-ctype": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Ctype\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gert de Pagter",
|
||||||
|
"email": "BackEndTea@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for ctype functions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"ctype",
|
||||||
|
"polyfill",
|
||||||
|
"portable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-ctype/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-mbstring",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||||
|
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"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.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-php80",
|
||||||
|
"version": "v1.31.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
|
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||||
|
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||||
|
"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.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": "vlucas/phpdotenv",
|
||||||
|
"version": "v5.6.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||||
|
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||||
|
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"graham-campbell/result-type": "^1.1.3",
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"phpoption/phpoption": "^1.9.3",
|
||||||
|
"symfony/polyfill-ctype": "^1.24",
|
||||||
|
"symfony/polyfill-mbstring": "^1.24",
|
||||||
|
"symfony/polyfill-php80": "^1.24"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||||
|
"ext-filter": "*",
|
||||||
|
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-filter": "Required to use the boolean validator."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"bamarni-bin": {
|
||||||
|
"bin-links": true,
|
||||||
|
"forward-command": false
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.6-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Dotenv\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vance Lucas",
|
||||||
|
"email": "vance@vancelucas.com",
|
||||||
|
"homepage": "https://github.com/vlucas"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||||
|
"keywords": [
|
||||||
|
"dotenv",
|
||||||
|
"env",
|
||||||
|
"environment"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||||
|
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-07-20T21:52:34+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
|
|||||||
BIN
requests.db
Normal file
BIN
requests.db
Normal file
Binary file not shown.
31
src/Bot.php
Normal file
31
src/Bot.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
use Clue\React\SQLite\Result;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
class Bot
|
||||||
|
{
|
||||||
|
|
||||||
|
// Helper Functions
|
||||||
|
public function isBot($userAgent): bool
|
||||||
|
{
|
||||||
|
$botKeywords = ['bot', 'crawl', 'spider', 'slurp', 'archive'];
|
||||||
|
foreach ($botKeywords as $keyword) {
|
||||||
|
if (stripos($userAgent, $keyword) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extractBotName($userAgent): string|null
|
||||||
|
{
|
||||||
|
preg_match('/bot|crawl|spider|slurp|archive/i', $userAgent, $matches);
|
||||||
|
return $matches[0] ?? null;
|
||||||
|
}}
|
||||||
78
src/Config.php
Normal file
78
src/Config.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Config|null
|
||||||
|
*/
|
||||||
|
protected static $instance;
|
||||||
|
public $db;
|
||||||
|
public $smarty;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->smarty = new \Smarty\Smarty();
|
||||||
|
$this->smarty->setTemplateDir(__DIR__ . '/../smarty/template/');
|
||||||
|
$this->smarty->setConfigDir(__DIR__ . '/../smarty/config/');
|
||||||
|
$this->smarty->setCompileDir(__DIR__ . '/../smarty/compile/');
|
||||||
|
$this->smarty->setCacheDir(__DIR__ . '/../smarty/cache/');
|
||||||
|
$this->smarty->setEscapeHtml(true);
|
||||||
|
$this->smarty->assign([
|
||||||
|
'baseURI' => $_ENV['BASEURI'],
|
||||||
|
]);
|
||||||
|
$this->smarty->compile_check = 1;
|
||||||
|
/* $this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/bots.db');
|
||||||
|
'uaCache' => new React\Cache\ArrayCache(1000),
|
||||||
|
'ipCache' => new React\Cache\ArrayCache(1000),
|
||||||
|
'headerCache' => new React\Cache\ArrayCache(1000),
|
||||||
|
'domainCache' => new React\Cache\ArrayCache(1000),
|
||||||
|
'pathCache' => new React\Cache\ArrayCache(1000), */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Config
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (empty(self::$instance)) self::$instance = new self();
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function registerAssetRoutes(&$app)
|
||||||
|
{
|
||||||
|
// Define the directory to scan
|
||||||
|
$assetsDir = realpath(__DIR__ . '/../public/assets');
|
||||||
|
|
||||||
|
if ($assetsDir === false) {
|
||||||
|
throw new \Exception('Assets directory not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a recursive directory iterator to scan all files
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($assetsDir)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Iterate through all files in the assets directory
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->isFile()) {
|
||||||
|
|
||||||
|
// Get the relative path of the file
|
||||||
|
$relativePath = str_replace($assetsDir, '', $file->getRealPath());
|
||||||
|
|
||||||
|
// Register the route
|
||||||
|
$route = $_ENV['BASEURI'] . '/assets' . str_replace('\\', '/', $relativePath);
|
||||||
|
|
||||||
|
$app->get($route, Controllers\StaticFilesController::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
63
src/Controllers/StaticFilesController.php
Normal file
63
src/Controllers/StaticFilesController.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl\Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class StaticFilesController
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between file extension and MIME type to send in `Content-Type` response header
|
||||||
|
*
|
||||||
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
private $mimetypes = array(
|
||||||
|
'atom' => 'application/atom+xml',
|
||||||
|
'bz2' => 'application/x-bzip2',
|
||||||
|
'css' => 'text/css',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'gz' => 'application/gzip',
|
||||||
|
'htm' => 'text/html',
|
||||||
|
'html' => 'text/html',
|
||||||
|
'ico' => 'image/x-icon',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'js' => 'text/javascript',
|
||||||
|
'json' => 'application/json',
|
||||||
|
'pdf' => 'application/pdf',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'rss' => 'application/rss+xml',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
'tar' => 'application/x-tar',
|
||||||
|
'xml' => 'application/xml',
|
||||||
|
'zip' => 'application/zip',
|
||||||
|
);
|
||||||
|
public function __invoke(ServerRequestInterface $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
$path = $request->getUri()->getPath();
|
||||||
|
$cleanedPath = $_ENV['APP_DIR'] . '/public' . str_replace($_ENV['BASEURI'], '', $path);
|
||||||
|
|
||||||
|
$stream = new \React\Stream\ReadableResourceStream(fopen($cleanedPath, 'r'), null, 65536);
|
||||||
|
$ext = strtolower(substr($path, strrpos($path, '.') + 1));
|
||||||
|
|
||||||
|
return new \React\Http\Message\Response(
|
||||||
|
\React\Http\Message\Response::STATUS_OK,
|
||||||
|
[
|
||||||
|
'Content-Type' => $this->mimetypes[$ext] ?? 'text/html',
|
||||||
|
'Cache-Control' => 'max-age=15552000',
|
||||||
|
'Content-length' => filesize($cleanedPath),
|
||||||
|
'Expires' => gmdate('D, d M Y H:i:s T', strtotime('next month')),
|
||||||
|
'Date' => gmdate('D, d M Y H:i:s T', filemtime($cleanedPath)),
|
||||||
|
'Last-modified' => gmdate('D, d M Y H:i:s T',filectime($cleanedPath))
|
||||||
|
|
||||||
|
],
|
||||||
|
$stream
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
187
src/IPMatch.php
Normal file
187
src/IPMatch.php
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
use Clue\React\SQLite\Result;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
|
||||||
|
class IPMatch
|
||||||
|
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Main function to check if a given IP (IPv4 or IPv6) matches a whitelist of ranges from the database.
|
||||||
|
*
|
||||||
|
* @param string $ip The IP address to check.
|
||||||
|
* @param object $db The database connection object (ReactPHP-based).
|
||||||
|
* @return \React\Promise\Promise Promise resolving to true if the IP matches any of the whitelist ranges, false otherwise.
|
||||||
|
*/
|
||||||
|
public static function checkIPWhitelistAsync(string $ip, $db): Promise
|
||||||
|
{
|
||||||
|
return self::getIPVersion($ip)->then(
|
||||||
|
function ($ipVersion) use ($ip, $db) {
|
||||||
|
if ($ipVersion === null) {
|
||||||
|
return false; // Invalid IP
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::fetchWhitelistRanges($db, $ipVersion)->then(
|
||||||
|
function ($ranges) use ($ip, $ipVersion) {
|
||||||
|
if ($ipVersion === 'ipv4') {
|
||||||
|
return self::checkIPv4($ip, $ranges);
|
||||||
|
} elseif ($ipVersion === 'ipv6') {
|
||||||
|
return self::checkIPv6($ip, $ranges);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the IP version (IPv4, IPv6, or null for invalid).
|
||||||
|
*
|
||||||
|
* @param string $ip The IP address to validate.
|
||||||
|
* @return \React\Promise\Promise Promise resolving to 'ipv4', 'ipv6', or null.
|
||||||
|
*/
|
||||||
|
private static function getIPVersion(string $ip): Promise
|
||||||
|
{
|
||||||
|
return new Promise(function (callable $resolve) use ($ip) {
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
$resolve('ipv4');
|
||||||
|
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
$resolve('ipv6');
|
||||||
|
} else {
|
||||||
|
$resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch whitelist ranges from the database for a specific IP version.
|
||||||
|
*
|
||||||
|
* @param object $db The database connection object.
|
||||||
|
* @param string $ipVersion The IP version ('ipv4' or 'ipv6').
|
||||||
|
* @return \React\Promise\Promise Promise resolving to an array of CIDR ranges.
|
||||||
|
*/
|
||||||
|
private static function fetchWhitelistRanges($db, string $ipVersion): Promise
|
||||||
|
{
|
||||||
|
return new Promise(function (callable $resolve, callable $reject) use ($db, $ipVersion) {
|
||||||
|
$query = 'SELECT range FROM whitelist WHERE ip_version = ?';
|
||||||
|
|
||||||
|
$db->query($query, [$ipVersion])->then(
|
||||||
|
function ($result) use ($resolve) {
|
||||||
|
$ranges = array_column($result->resultRows, 'range');
|
||||||
|
$resolve($ranges);
|
||||||
|
},
|
||||||
|
function ($error) use ($reject) {
|
||||||
|
$reject($error); // Reject if the query fails
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given IPv6 address is in a network from an array of ranges (asynchronous with ReactPHP Promise)
|
||||||
|
*
|
||||||
|
* @param string $ip IPv6 address to check
|
||||||
|
* @param array $ranges Array of IPv6/CIDR ranges, e.g., ['2001:db8::/32', '2001:0db8:85a3::8a2e:0370:7334/128']
|
||||||
|
* @return \React\Promise\Promise Promise resolving to true if the IPv6 is in any of the ranges, false otherwise
|
||||||
|
*/
|
||||||
|
public static function checkIPv6(string $ip, array $ranges): PromiseInterface
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
return new Promise(function (callable $resolve) use ($ip, $ranges) {
|
||||||
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
|
$resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ranges as $range) {
|
||||||
|
if (!is_string($range)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($range, '/') === false) {
|
||||||
|
$range .= '/128';
|
||||||
|
}
|
||||||
|
|
||||||
|
[$range_ip, $netmask] = explode('/', $range, 2);
|
||||||
|
if (!filter_var($range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || !is_numeric($netmask) || $netmask < 0 || $netmask > 128) {
|
||||||
|
continue; // Skip invalid ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip_bin = inet_pton($ip);
|
||||||
|
$range_bin = inet_pton($range_ip);
|
||||||
|
$netmask_bin = str_repeat("\xff", (int)($netmask / 8));
|
||||||
|
|
||||||
|
if ($netmask % 8 !== 0) {
|
||||||
|
$netmask_bin .= chr(0xff << (8 - ($netmask % 8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$netmask_bin = str_pad($netmask_bin, strlen($ip_bin), "\x00");
|
||||||
|
|
||||||
|
if (($ip_bin & $netmask_bin) === ($range_bin & $netmask_bin)) {
|
||||||
|
$resolve(true); // Resolve with true if a match is found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolve(false); // Resolve with false if no matches are found
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given IPv4 address is in a network from an array of ranges (asynchronous with ReactPHP Promise)
|
||||||
|
*
|
||||||
|
* @param string $ip IPv4 address to check
|
||||||
|
* @param array $ranges Array of IPv4/CIDR ranges, e.g., ['192.168.1.0/24', '10.0.0.1/32']
|
||||||
|
* @return \React\Promise\Promise Promise resolving to true if the IPv4 is in any of the ranges, false otherwise
|
||||||
|
*/
|
||||||
|
public static function checkIPv4(string $ip, array $ranges): Promise
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new Promise(function (callable $resolve) use ($ip, $ranges) {
|
||||||
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
$resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ranges as $range) {
|
||||||
|
if (!is_string($range)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($range, '/') === false) {
|
||||||
|
$range .= '/32';
|
||||||
|
}
|
||||||
|
|
||||||
|
[$range_ip, $netmask] = explode('/', $range, 2);
|
||||||
|
if (!filter_var($range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || !is_numeric($netmask) || $netmask < 0 || $netmask > 32) {
|
||||||
|
continue; // Skip invalid ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
$range_decimal = ip2long($range_ip);
|
||||||
|
$ip_decimal = ip2long($ip);
|
||||||
|
$netmask_decimal = -1 << (32 - (int)$netmask);
|
||||||
|
|
||||||
|
if (($ip_decimal & $netmask_decimal) === ($range_decimal & $netmask_decimal)) {
|
||||||
|
$resolve(true); // Resolve with true if a match is found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolve(false); // Resolve with false if no matches are found
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/InitTables.php
Normal file
53
src/InitTables.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
class InitTables
|
||||||
|
{
|
||||||
|
|
||||||
|
public static function create():PromiseInterface
|
||||||
|
{
|
||||||
|
$db = Storage::getInstance()->db;
|
||||||
|
return $db->exec("CREATE TABLE IF NOT EXISTS ip (data TEXT UNIQUE NOT NULL CHECK (data LIKE '%'), CONSTRAINT valid_ip CHECK (data LIKE '%.%' OR data LIKE '%:%')) STRICT ;")
|
||||||
|
->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS domain (data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS path (data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS useragent ( data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS headers ( data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec("CREATE TABLE IF NOT EXISTS networkwhitelist ( data TEXT UNIQUE NOT NULL CHECK (data LIKE '%/%'), CONSTRAINT valid_network CHECK ( data LIKE '%.%/%' OR data LIKE '%:%/%' )) STRICT ;");
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS request ( id_ip INTEGER NOT NULL, id_method INTEGER NOT NULL, id_domain INTEGER NOT NULL, id_path INTEGER NOT NULL, id_useragent INTEGER NOT NULL, id_headers INTEGER NOT NULL, timestamp INTEGER NOT NULL, FOREIGN KEY (id_ip) REFERENCES ip(rowid), FOREIGN KEY (id_domain) REFERENCES domain(rowid), FOREIGN KEY (id_path) REFERENCES path(rowid), FOREIGN KEY (id_useragent) REFERENCES useragent(rowid), FOREIGN KEY (id_headers) REFERENCES headers(rowid) ) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS bot ( name TEXT NOT NULL, keyword TEXT NULL ) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('PRAGMA journal_mode=WAL;');
|
||||||
|
});
|
||||||
|
|
||||||
|
return $db->exec("CREATE TABLE IF NOT EXISTS ip (id_ip INTEGER PRIMARY KEY AUTOINCREMENT, ip TEXT UNIQUE NOT NULL CHECK (ip LIKE '%'), CONSTRAINT valid_ip CHECK (ip LIKE '%.%' OR ip LIKE '%:%')) STRICT ;")
|
||||||
|
->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS domain (id_domain INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS path ( id_path INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS useragent ( id_useragent INTEGER PRIMARY KEY AUTOINCREMENT, useragent TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS headers ( id_headers INTEGER PRIMARY KEY AUTOINCREMENT, headers TEXT UNIQUE NOT NULL) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec("CREATE TABLE IF NOT EXISTS networkwhitelist ( id_networkwhitelist INTEGER PRIMARY KEY AUTOINCREMENT, network TEXT UNIQUE NOT NULL CHECK (network LIKE '%/%'), CONSTRAINT valid_network CHECK ( network LIKE '%.%/%' OR network LIKE '%:%/%' )) STRICT WITHOUT ROWID ;");
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS request ( id_request INTEGER PRIMARY KEY AUTOINCREMENT, id_ip INTEGER NOT NULL, id_method INTEGER NOT NULL, id_domain INTEGER NOT NULL, id_path INTEGER NOT NULL, id_useragent INTEGER NOT NULL, id_headers INTEGER NOT NULL, timestamp INTEGER NOT NULL, FOREIGN KEY (id_ip) REFERENCES ip(id_ip), FOREIGN KEY (id_domain) REFERENCES domain(id_domain), FOREIGN KEY (id_path) REFERENCES path(id_path), FOREIGN KEY (id_useragent) REFERENCES useragent(id_useragent), FOREIGN KEY (id_headers) REFERENCES headers(id_headers) ) STRICT WITHOUT ROWID ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('CREATE TABLE IF NOT EXISTS bot ( id_bot INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, keyword TEXT NULL ) STRICT ;');
|
||||||
|
})->then(function () use ($db) {
|
||||||
|
return $db->exec('PRAGMA journal_mode=WAL;');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/Request.php
Normal file
78
src/Request.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
use Clue\React\SQLite\Result;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
use React\Promise\Promise;
|
||||||
|
|
||||||
|
class Request
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
const METHOD = [
|
||||||
|
'GET' => 1,
|
||||||
|
'HEAD' => 2,
|
||||||
|
'OPTIONS' => 3,
|
||||||
|
'TRACE' => 4,
|
||||||
|
'PUT' => 5,
|
||||||
|
'DELETE' => 6,
|
||||||
|
'POST' => 7,
|
||||||
|
'PATCH' => 8,
|
||||||
|
'CONNECT' => 9
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static function save(ServerRequestInterface $request): PromiseInterface
|
||||||
|
{
|
||||||
|
$realIp = self::getRealIP($request);
|
||||||
|
$userAgent = $request->getHeaderLine('User-Agent') ?: 'Unknown';
|
||||||
|
$headers = json_encode($request->getHeaders(), JSON_UNESCAPED_UNICODE);
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$storage = Storage::getInstance();
|
||||||
|
|
||||||
|
// Use parallel promises for ID generation to avoid waiting for each in sequence
|
||||||
|
$idPromises = [
|
||||||
|
'id_ip' => $storage::getId('ip', $realIp),
|
||||||
|
'id_domain' => $storage::getId('domain', $uri->getHost()),
|
||||||
|
'id_path' => $storage::getId('path', $uri->getPath()),
|
||||||
|
'id_useragent' => $storage::getId('useragent', $userAgent),
|
||||||
|
'id_headers' => $storage::getId('headers', md5($headers)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return \React\Promise\all($idPromises)
|
||||||
|
->then(function ($resolvedValues) use ($request, $storage) {
|
||||||
|
// Set resolved values efficiently
|
||||||
|
$resolvedValues['id_method'] = self::METHOD[$request->getMethod()] ?? 0;
|
||||||
|
$resolvedValues['timestamp'] = time();
|
||||||
|
|
||||||
|
// Directly save data asynchronously
|
||||||
|
return $storage::insert('request', $resolvedValues);
|
||||||
|
})
|
||||||
|
->then(function () {
|
||||||
|
return \React\Http\Message\Response::plaintext('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRealIP(ServerRequestInterface $request): string
|
||||||
|
{
|
||||||
|
$cfConnectingIp = $request->getHeaderLine('CF-Connecting-IP');
|
||||||
|
if (!empty($cfConnectingIp)) {
|
||||||
|
return $cfConnectingIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
$xForwardedFor = $request->getHeaderLine('X-Forwarded-For');
|
||||||
|
if (!empty($xForwardedFor)) {
|
||||||
|
return explode(',', $xForwardedFor)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$remoteAddr = $request->getServerParams()['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||||
|
return $remoteAddr;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/Storage.php
Normal file
100
src/Storage.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace XBotControl;
|
||||||
|
|
||||||
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
use Clue\React\SQLite\Result;
|
||||||
|
|
||||||
|
class Storage
|
||||||
|
{
|
||||||
|
protected static $instance;
|
||||||
|
/** @var DatabaseInterface $db */
|
||||||
|
public $db;
|
||||||
|
public $cache = [];
|
||||||
|
|
||||||
|
protected static $tablesCache = [
|
||||||
|
'ip',
|
||||||
|
'domain',
|
||||||
|
'useragent',
|
||||||
|
'headers',
|
||||||
|
'path'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/requests.db');
|
||||||
|
|
||||||
|
foreach (self::$tablesCache as $cacheParition) {
|
||||||
|
$this->cache[$cacheParition] = new \React\Cache\ArrayCache(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance(): Storage
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getId(string $cacheParition, string $key): PromiseInterface
|
||||||
|
{
|
||||||
|
$storage = self::getInstance();
|
||||||
|
|
||||||
|
/* if (!isset(self::$tablesCache[$cacheParition])) {
|
||||||
|
return $storage::insert($cacheParition, ['data' => $key])->then(function ($result) {
|
||||||
|
return $result->rows["0"][$result->columns['0']];
|
||||||
|
});
|
||||||
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
return $storage->cache[$cacheParition]->get($key)
|
||||||
|
->then(function ($result) use ($storage, $cacheParition, $key) {
|
||||||
|
if ($result === null) {
|
||||||
|
return self::insertAndCache($storage, $cacheParition, $key);
|
||||||
|
}
|
||||||
|
return (int) $result;
|
||||||
|
}, function () {
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function insertAndCache(Storage $storage, string $cacheParition, string $key): PromiseInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
$query = "INSERT INTO $cacheParition (data) VALUES (?) ON CONFLICT(data) DO UPDATE SET data=? RETURNING rowid ;";
|
||||||
|
|
||||||
|
return $storage->db
|
||||||
|
->query($query, [$key, $key])
|
||||||
|
->then(function (Result $result) use ($storage, $cacheParition, $key) {
|
||||||
|
|
||||||
|
return self::cache($storage, $cacheParition, $key, $result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function cache(Storage $storage, string $cacheParition, string $key, Result $result): PromiseInterface
|
||||||
|
{
|
||||||
|
$value = $result->rows["0"][$result->columns['0']];
|
||||||
|
return $storage->cache[$cacheParition]->set($key, $value)
|
||||||
|
->then(function () use ($value) {
|
||||||
|
return $value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function insert(string $table, array $values): PromiseInterface
|
||||||
|
{
|
||||||
|
$columns = implode(", ", array_keys($values));
|
||||||
|
$placeholders = implode(", ", array_fill(0, count($values), "?"));
|
||||||
|
$query = sprintf("INSERT INTO %s (%s) VALUES (%s);", $table, $columns, $placeholders);
|
||||||
|
$params = array_values($values);
|
||||||
|
|
||||||
|
$storage = self::getInstance();
|
||||||
|
return $storage->db->query($query, $params)->then(function (Result $result) {
|
||||||
|
return $result->insertId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
41
xbotcontrol.php
Normal file
41
xbotcontrol.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
|
||||||
|
$dotenv->load();
|
||||||
|
$_ENV['APP_DIR'] = __DIR__;
|
||||||
|
$_ENV['X_LISTEN'] = '0.0.0.0:7500';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
XBotControl\InitTables::create();
|
||||||
|
$app = new FrameworkX\App();
|
||||||
|
|
||||||
|
$app->get(
|
||||||
|
'/mirror',
|
||||||
|
function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||||
|
return XBotControl\Request::save($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$app->get('/mirror1', function () {
|
||||||
|
return React\Http\Message\Response::plaintext(
|
||||||
|
"Hello wörld!\n"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->get('/cp', function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||||
|
return React\Http\Message\Response::plaintext(
|
||||||
|
"Hello " . $request->getAttribute('name') . "!\n"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->run();
|
||||||
|
|
||||||
|
XBotControl\Storage::getInstance()->db->query('PRAGMA main.wal_checkpoint;')
|
||||||
|
->then(function () {
|
||||||
|
XBotControl\Storage::getInstance()->db->quit();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user