commit 18399914571ca740a0d1f94b202e8a824ff8c540 Author: flashwave Date: Sat Dec 2 01:24:13 2023 +0000 Initial import. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a581b2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +[Tt]humbs.db +[Dd]esktop.ini +.DS_Store +/.idea +/vendor +/config.php +/composer.phar diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4b1ed85 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "cboden/ratchet": "0.3.*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..357bafc --- /dev/null +++ b/composer.lock @@ -0,0 +1,1062 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "858ccf2b00629904fbfcaf8568d3cb4b", + "packages": [ + { + "name": "cboden/ratchet", + "version": "v0.3.6", + "source": { + "type": "git", + "url": "https://github.com/ratchetphp/Ratchet.git", + "reference": "84df35d2a6576985b9e81b564d3c25809f8d647e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ratchetphp/Ratchet/zipball/84df35d2a6576985b9e81b564d3c25809f8d647e", + "reference": "84df35d2a6576985b9e81b564d3c25809f8d647e", + "shasum": "" + }, + "require": { + "guzzle/http": "^3.6", + "php": ">=5.3.9", + "react/socket": "^0.3 || ^0.4", + "symfony/http-foundation": "^2.2|^3.0", + "symfony/routing": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ratchet\\": "src/Ratchet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "role": "Developer" + } + ], + "description": "PHP WebSocket library", + "homepage": "http://socketo.me", + "keywords": [ + "Ratchet", + "WebSockets", + "server", + "sockets" + ], + "support": { + "forum": "https://groups.google.com/forum/#!forum/ratchet-php", + "irc": "irc://irc.freenode.org/reactphp", + "issues": "https://github.com/ratchetphp/Ratchet/issues", + "source": "https://github.com/ratchetphp/Ratchet/tree/master" + }, + "time": "2017-01-06T14:36:36+00:00" + }, + { + "name": "evenement/evenement", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/6ba9a777870ab49f417e703229d53931ed40fd7a", + "reference": "6ba9a777870ab49f417e703229d53931ed40fd7a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0||^5.7||^4.8.35" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/master" + }, + "time": "2017-07-17T17:39:19+00:00" + }, + { + "name": "guzzle/common", + "version": "v3.9.2", + "target-dir": "Guzzle/Common", + "source": { + "type": "git", + "url": "https://github.com/Guzzle3/common.git", + "reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Guzzle3/common/zipball/2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc", + "reference": "2e36af7cf2ce3ea1f2d7c2831843b883a8e7b7dc", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "symfony/event-dispatcher": ">=2.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle\\Common": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Common libraries used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "collection", + "common", + "event", + "exception" + ], + "support": { + "source": "https://github.com/Guzzle3/common/tree/v3.9.2" + }, + "abandoned": "guzzle/guzzle", + "time": "2014-08-11T04:32:36+00:00" + }, + { + "name": "guzzle/http", + "version": "v3.9.2", + "target-dir": "Guzzle/Http", + "source": { + "type": "git", + "url": "https://github.com/Guzzle3/http.git", + "reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Guzzle3/http/zipball/1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", + "reference": "1e8dd1e2ba9dc42332396f39fbfab950b2301dc5", + "shasum": "" + }, + "require": { + "guzzle/common": "self.version", + "guzzle/parser": "self.version", + "guzzle/stream": "self.version", + "php": ">=5.3.2" + }, + "suggest": { + "ext-curl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle\\Http": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "HTTP libraries used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "client", + "curl", + "http", + "http client" + ], + "support": { + "source": "https://github.com/Guzzle3/http/tree/v3.9.2" + }, + "abandoned": "guzzle/guzzle", + "time": "2014-08-11T04:32:36+00:00" + }, + { + "name": "guzzle/parser", + "version": "v3.9.2", + "target-dir": "Guzzle/Parser", + "source": { + "type": "git", + "url": "https://github.com/Guzzle3/parser.git", + "reference": "6874d171318a8e93eb6d224cf85e4678490b625c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Guzzle3/parser/zipball/6874d171318a8e93eb6d224cf85e4678490b625c", + "reference": "6874d171318a8e93eb6d224cf85e4678490b625c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle\\Parser": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Interchangeable parsers used by Guzzle", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "URI Template", + "cookie", + "http", + "message", + "url" + ], + "support": { + "source": "https://github.com/Guzzle3/parser/tree/v3.9.2" + }, + "abandoned": "guzzle/guzzle", + "time": "2014-02-05T18:29:46+00:00" + }, + { + "name": "guzzle/stream", + "version": "v3.9.2", + "target-dir": "Guzzle/Stream", + "source": { + "type": "git", + "url": "https://github.com/Guzzle3/stream.git", + "reference": "60c7fed02e98d2c518dae8f97874c8f4622100f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Guzzle3/stream/zipball/60c7fed02e98d2c518dae8f97874c8f4622100f0", + "reference": "60c7fed02e98d2c518dae8f97874c8f4622100f0", + "shasum": "" + }, + "require": { + "guzzle/common": "self.version", + "php": ">=5.3.2" + }, + "suggest": { + "guzzle/http": "To convert Guzzle request objects to PHP streams" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7-dev" + } + }, + "autoload": { + "psr-0": { + "Guzzle\\Stream": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle stream wrapper component", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "component", + "stream" + ], + "support": { + "source": "https://github.com/Guzzle3/stream/tree/v3.9.2" + }, + "abandoned": "guzzle/guzzle", + "time": "2014-05-01T21:36:02+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "react/event-loop", + "version": "v0.4.3", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/8bde03488ee897dc6bb3d91e4e17c353f9c5252f", + "reference": "8bde03488ee897dc6bb3d91e4e17c353f9c5252f", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "suggest": { + "ext-event": "~1.0", + "ext-libev": "*", + "ext-libevent": ">=0.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event loop abstraction layer that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/0.4" + }, + "time": "2017-04-27T10:56:23+00:00" + }, + { + "name": "react/promise", + "version": "v2.11.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/1a8460931ea36dc5c76838fec5734d55c88c6831", + "reference": "1a8460931ea36dc5c76838fec5734d55c88c6831", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.11.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:16:50+00:00" + }, + { + "name": "react/socket", + "version": "v0.4.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "cf074e53c974df52388ebd09710a9018894745d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/cf074e53c974df52388ebd09710a9018894745d2", + "reference": "cf074e53c974df52388ebd09710a9018894745d2", + "shasum": "" + }, + "require": { + "evenement/evenement": "~2.0|~1.0", + "php": ">=5.3.0", + "react/event-loop": "0.4.*|0.3.*", + "react/promise": "^2.0 || ^1.1", + "react/stream": "^0.4.5" + }, + "require-dev": { + "clue/block-react": "^1.1", + "phpunit/phpunit": "~4.8", + "react/socket-client": "^0.5.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server for React PHP", + "keywords": [ + "Socket" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v0.4.6" + }, + "time": "2017-01-26T09:23:38+00:00" + }, + { + "name": "react/stream", + "version": "v0.4.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/44dc7f51ea48624110136b535b9ba44fd7d0c1ee", + "reference": "44dc7f51ea48624110136b535b9ba44fd7d0c1ee", + "shasum": "" + }, + "require": { + "evenement/evenement": "^2.0|^1.0", + "php": ">=5.3.8" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "react/event-loop": "^0.4|^0.3", + "react/promise": "^2.0|^1.0" + }, + "suggest": { + "react/event-loop": "^0.4", + "react/promise": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Basic readable and writable stream interfaces that support piping.", + "keywords": [ + "pipe", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v0.4.6" + }, + "time": "2017-01-25T14:44:14+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" + }, + "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": "2023-07-06T06:56:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.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": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b9885fcce6fe494201da4f70a9309770e9d13dc8", + "reference": "b9885fcce6fe494201da4f70a9309770e9d13dc8", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" + }, + "require-dev": { + "symfony/expression-language": "~2.8|~3.0|~4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v3.4.47" + }, + "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": "2020-10-24T10:57:07+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/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.28.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": "2023-07-28T09:04:16+00:00" + }, + { + "name": "symfony/polyfill-php70", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "metapackage", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "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 7.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.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": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/routing", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/3e522ac69cadffd8131cc2b22157fa7662331a6c", + "reference": "3e522ac69cadffd8131cc2b22157fa7662331a6c", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "conflict": { + "symfony/config": "<3.3.1", + "symfony/dependency-injection": "<3.3", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "^3.3.1|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/http-foundation": "~2.8|~3.0|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v3.4.47" + }, + "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": "2020-10-24T10:57:07+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/daemon b/daemon new file mode 100644 index 0000000..9699f58 --- /dev/null +++ b/daemon @@ -0,0 +1,45 @@ +#!/bin/bash + +function spawn () { + nohup php server.php > daemon.out 2>&1 & + PID=$! + echo $PID > daemon.pid +} + +if [ -z $1 ] +then + if [ ! -e daemon.pid ] + then + nohup bash $0 start > /dev/null 2>&1 & + echo "Sock Chat Daemon started." + else + echo "ERROR: Sock Chat Daemon is already running!" + echo "To stop the daemon, use daemon stop" + fi +elif [ $1 = "start" ] +then + if [ ! -e daemon.pid ] + then + spawn + + while [ -e daemon.pid ] + do + if ! ps -p $PID > /dev/null + then + echo "Server died unexpectedly! Restarting..." + spawn + fi + sleep 5 + done + + /bin/kill -s SIGKILL $PID + fi +elif [ $1 = "stop" ] +then + rm daemon.pid + echo "Sock Chat Daemon stopped." +else + echo "SOCK CHAT SERVER DAEMON" + echo + echo "USAGE: daemon [stop]" +fi diff --git a/daemon.out b/daemon.out new file mode 100644 index 0000000..b09c538 --- /dev/null +++ b/daemon.out @@ -0,0 +1,10 @@ +Could not connect to the database! Details: SQLSTATE[HY000] [1045] Access denied for user 'aroltd'@'localhost' (using password: YES) +PHP Fatal error: Uncaught PDOException: SQLSTATE[HY000] [1045] Access denied for user 'aroltd'@'localhost' (using password: YES) in /home/flash/nabucco/lib/db.php:121 +Stack trace: +#0 /home/flash/nabucco/lib/db.php(121): PDO->__construct('mysql:host=loca...', 'aroltd', 'buddyman5', Array) +#1 /home/flash/nabucco/lib/db.php(106): sockchat\Database::SpawnConnection() +#2 /home/flash/nabucco/lib/db.php(207): sockchat\Database::Execute('clrusers') +#3 /home/flash/nabucco/server.php(32): sockchat\Database::TruncateUserList() +#4 /home/flash/nabucco/server.php(149): sockchat\Chat->__construct() +#5 {main} + thrown in /home/flash/nabucco/lib/db.php on line 121 diff --git a/lib/channel.php b/lib/channel.php new file mode 100644 index 0000000..1666328 --- /dev/null +++ b/lib/channel.php @@ -0,0 +1,65 @@ +logs, [$time == null ? gmdate("U") : $time, clone $user, $msg, $msgid, $flags]); + if(count($this->logs) > Backlog::$loglen) + $this->logs = array_slice($this->logs, 1); + } + + public function GetAllLogStrings() { + $retval = array(); + foreach($this->logs as $msg) + array_push($retval, join(Utils::$separator, array($msg[0], $msg[1], $msg[2], $msg[3], "0", $msg[4]))); + //$retval = array_reverse($retval); + return $retval; + } +} + +class Channel { + public $name; + public $permissionLevel = 0; + + public $password = ""; + public $users = []; + public $channelMods = []; // id list + + public $channelOwner = null; + public $channelType = CHANNEL_PERM; + + public $log; + + public function __construct($name, $password = "", $permissionLevel = 0, $channelOwner = null, $channelType = CHANNEL_PERM, $backlog = null) { + $this->name = $name; + $this->permissionLevel = $permissionLevel; + $this->password = $password; + $this->channelOwner = $channelOwner; + $this->channelType = $channelType; + $this->log = $backlog == null ? new Backlog() : $backlog; + } + + public function GetOwner() { + if($this->channelOwner != null) return $this->channelOwner; + else return new User(-1, "", "", "", "", null); + } + + public function GetAllUsers() { + $arr = []; + + foreach(Context::$invisibleUsers as $user) + array_push($arr, $user . Utils::$separator . "0"); + foreach($this->users as $user) + array_push($arr, $user . Utils::$separator . "1"); + + return (count($this->users) + count(Context::$invisibleUsers)) . Utils::$separator . join(Utils::$separator, $arr); + } + + public function __toString() { + return join(Utils::$separator, [$this->name, $this->password != "" ? "1" : "0", $this->channelType == CHANNEL_TEMP ? "1" : "0"]); + } +} \ No newline at end of file diff --git a/lib/constants.php b/lib/constants.php new file mode 100644 index 0000000..6508bd3 --- /dev/null +++ b/lib/constants.php @@ -0,0 +1,39 @@ +id = Utils::$chat["AUTOID"] ? null : $id; + $this->ip = $ip; + $this->username = $username; + $this->expire = $expire; + } + + public function Check($id, $ip, $username) { + if($GLOBALS["chat"]["AUTOID"]) $id = null; + return (($this->id == null ? false : $id == $this->id) || + ($this->ip == null ? false : Utils::CheckIPAddresses($ip, $this->ip)) || + ($this->username == null ? false : $username == $this->username)) && + ($this->expire > time() || $this->expire == -1); + } +} + +class Context { + public static $onlineUsers = []; + public static $channelList = []; + public static $bannedUsers = []; + public static $invisibleUsers = []; + + public static function ForceChannelSwitch($user, $to) { + if(Context::ChannelExists($to)) { + $oldchan = $user->channel; + + if(!Modules::ExecuteRoutine("OnChannelDelete", [$user, Context::GetChannel($to), Context::GetChannel($oldchan)])) return; + Message::HandleChannelSwitch($user, $to, $user->channel); + unset(Context::GetChannel($user->channel)->users[$user->id]); + Context::GetChannel($to)->users[$user->id] = Context::$onlineUsers[$user->id]; + Context::$onlineUsers[$user->id]->channel = $to; + + if(Context::GetChannel($oldchan)->channelType == CHANNEL_TEMP && Context::GetChannel($oldchan)->GetOwner()->id == $user->id) + Context::DeleteChannel($oldchan); + + Modules::ExecuteRoutine("AfterChannelSwitch", [$user, Context::GetChannel($to), Context::GetChannel($oldchan)]); + } + } + + public static function SwitchChannel($user, $to, $pwd = "") { + if($user->channel != $to) { + if(Context::ChannelExists($to)) { + if($pwd == Context::GetChannel($to)->password || $user->canModerate() || Context::GetChannel($to)->GetOwner()->id == $user->id) { + if(Context::GetChannel($to)->permissionLevel <= $user->getRank()) { + Context::ForceChannelSwitch($user, $to); + return; + } else Message::PrivateBotMessage(MSG_ERROR, "ipchan", array($to), $user); + } else Message::PrivateBotMessage(MSG_ERROR, "ipwchan", array($to), $user); + } else Message::PrivateBotMessage(MSG_ERROR, "nochan", array($to), $user); + } // else Message::PrivateBotMessage(MSG_ERROR, "samechan", array($to), $user); // kind of extraneous + $user->sock->send(Utils::PackMessage(5, ["2", $user->channel])); + } + + public static function IsLobby($channel) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + return $channel->name == Context::GetChannel(Utils::$chat["DEFAULT_CHANNEL"])->name; + } + + public static function AddInvisibleUser($name, $color) { + for($id = -2;;$id--) { + if(!array_key_exists($id, Context::$onlineUsers) && !array_key_exists($id, Context::$invisibleUsers)) break; + } + Context::$invisibleUsers[$id] = new User($id, "", $name, $color, "6770\f1\f1\f1\f1\f1", null, false); + foreach(Context::$onlineUsers as $user) { + $user->sock->send(Utils::PackMessage(7, ["1", $id, $name, $color, "6770\f1\f1\f1\f1\f1", ])); + } + return Context::$invisibleUsers[$id]; + } + + public static function GetUserByID($id) { + if(array_key_exists($id, Context::$onlineUsers)) return Context::$onlineUsers[$id]; + //return Context::$onlineUsers[array_rand(Context::$onlineUsers)]; + else return null; + } + + public static function GetUserByName($name) { + foreach(Context::$onlineUsers as $user) { + if ($user->username == $name) return $user; + } + + //return GetUserByID(0); + + return null; + } + + public static function GetUserBySock($sock) { + foreach(Context::$onlineUsers as $user) { + if($user->sock == $sock) return $user; + } + + return null; + } + + public static function GetAllChannels() { + return join(Utils::$separator, Context::$channelList); + } + + public static function GetChannel($name) { + if(array_key_exists($name, Context::$channelList)) return Context::$channelList[$name]; + else if($name == "@default") return Context::$channelList[Utils::$chat["DEFAULT_CHANNEL"]]; + else return null; + } + + public static function ChannelExists($name) { + return array_key_exists($name, Context::$channelList); + } + + public static function CreateChannel($channel) { + if(is_string($channel)) $channel = new Channel($channel); + + if(!Context::ChannelExists($channel->name)) { + if($channel->name[0] != "@" && $channel->name[0] != "*") { + if(!Modules::ExecuteRoutine("OnChannelCreate", [$channel])) return Utils::FormatBotMessage(MSG_ERROR, "generr", []); + Context::$channelList[$channel->name] = $channel; + Message::HandleChannelCreation($channel); + Database::CreateChannel($channel->name, $channel->password, $channel->permissionLevel); + Modules::ExecuteRoutine("AfterChannelCreate", [$channel]); + return "OK"; + } else return Utils::FormatBotMessage(MSG_ERROR, "inchan", []); + } else return Utils::FormatBotMessage(MSG_ERROR, "nischan", [$channel->name]); + } + + public static function RenameChannel($oldname, $newname) { + if(Context::ChannelExists($oldname) && !Context::ChannelExists($newname)) { + Context::$channelList[$newname] = clone Context::GetChannel($oldname); + Context::$channelList[$newname]->name = $newname; + if(!Modules::ExecuteRoutine("OnChannelModify", [Context::$channelList[$oldname], Context::$channelList[$newname]])) { + unset(Context::$channelList[$newname]); + return false; + } + Message::HandleChannelModification($newname, $oldname); + Modules::ExecuteRoutine("AfterChannelModify", [Context::$channelList[$oldname], Context::$channelList[$newname]]); + unset(Context::$channelList[$oldname]); + return true; + } else return false; + } + + public static function ChangeChannelPassword($channel, $pwd) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + $tmp = [clone $channel, clone $channel]; + $tmp[0]->password = trim($pwd) == "" ? "" : Utils::Hash(trim($pwd)); + if(!Modules::ExecuteRoutine("OnChannelModify", [$channel, $tmp[0]])) return; + Context::$channelList[$channel->name] = $tmp[0]; + Message::HandleChannelModification(Context::$channelList[$channel->name]); + Modules::ExecuteRoutine("AfterChannelModify", [$tmp[1], $channel]); + } + + public static function ChangeChannelPermission($channel, $perm) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + $tmp = [clone $channel, clone $channel]; + $tmp[0]->permissionLevel = $perm; + if(!Modules::ExecuteRoutine("OnChannelModify", [$channel, $tmp[0]])) return; + Context::$channelList[$channel->name] = $tmp[0]; + Message::HandleChannelModification(Context::$channelList[$channel->name]); + Modules::ExecuteRoutine("AfterChannelModify", [$tmp[1], $channel]); + } + + public static function DeleteChannel($channel) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + if(!Modules::ExecuteRoutine("OnChannelDelete", [$channel])); + foreach($channel->users as $user) Context::SwitchChannel($user, Utils::$chat["DEFAULT_CHANNEL"]); + Message::HandleChannelDeletion($channel); + Database::RemoveChannel($channel->name); + unset(Context::$channelList[$channel->name]); + Modules::ExecuteRoutine("AfterChannelDelete", [$channel]); + } + + public static function Join($user) { + if(!Modules::ExecuteRoutine("OnUserJoin", [$user])) return; + Message::HandleJoin($user); + Context::$onlineUsers[$user->id] = $user; + Context::$channelList[Utils::$chat["DEFAULT_CHANNEL"]]->users[$user->id] = Context::$onlineUsers[$user->id]; + Database::Login($user); + Modules::ExecuteRoutine("AfterUserJoin", [$user]); + } + + public static function AllowUser($username, $sock) { + foreach(Context::$onlineUsers as $user) { + if($user->GetOriginalUsername() != $username) { + if($sock == $user->sock) { + return "sockfail"; + } + } else return "userfail"; + } + return 0; + } + + public static function CheckBan($id, $ip, $name) { + foreach(Context::$bannedUsers as $ban) { + if($ban->Check($id, $ip, $name)) return $ban->expire; + } + + return false; + } + + public static function Unban($id, $ip, $name, $by = null) { + if($by == null) $by = Message::$bot; + if(!Modules::ExecuteRoutine("OnUnban", [&$id, &$ip, &$name, $by])) return false; + $banned = false; + foreach(Context::$bannedUsers as $bid => $ban) { + if($ban->Check($id, $ip, $name)) { + unset(Context::$bannedUsers[$bid]); + $banned = true; + } + } + if($ip != null) $ip = str_replace("*", "%", $ip); + Database::Unban($ip, $id, $name); + Modules::ExecuteRoutine("AfterUnban", [$id, $ip, $name, $by]); + return $banned; + } + + public static function ModifyUser($newuser) { + $u = Context::GetUserByID($newuser->id); + $u->Copy($newuser); + if(!Modules::ExecuteRoutine("OnUserModify", [$u])) return; + Message::HandleUserModification($u); + } + + public static function KickUser($user, $by = null, $time = 0, $banip = false, $type = LEAVE_KICK) { + if(!Modules::ExecuteRoutine("OnUserKick", [$user, $by == null ? Message::$bot : $by, &$time, &$banip])) return; + Message::HandleKick($user, $time); + if($time != 0) { + $exp = $time < 0 ? -1 : (int)gmdate("U") + $time; + Database::Ban($banip ? $user->sock->remoteAddress : null , $user->id, $user->GetOriginalUsername(), $exp); + array_push(Context::$bannedUsers, new Ban($banip ? $user->sock->remoteAddress : null , $user->id, $user->GetOriginalUsername(), $exp)); + } + $ip = $user->sock->remoteAddress; + $user->sock->close(); + Context::Leave($user, $type); + if($banip) Context::BanIP($ip, $time, $by, true); + Modules::ExecuteRoutine("AfterUserKick", [$user, $by == null ? Message::$bot : $by, $time, $banip]); + } + + public static function BanIP($ip, $time = -1, $by = null, $alreadybanned = false) { + if(!$alreadybanned) { + if(!Modules::ExecuteRoutine("OnBanIP", [&$ip, &$time, $by == null ? Message::$bot : $by])) return; + $exp = $time < 0 ? -1 : (int)gmdate("U") + $time; + array_push(Context::$bannedUsers, new Ban($ip, null, null, $exp)); + Database::Ban($ip, null, null, $exp); + } + + foreach(Context::$onlineUsers as $user) { + if(Utils::CheckIPAddresses($user->sock->remoteAddress, $ip)) { + Message::HandleKick($user, $time); + $user->sock->close(); + Context::Leave($user, LEAVE_KICK); + } + } + + if(!$alreadybanned) + Modules::ExecuteRoutine("AfterBanIP", [$ip, $time, $by == null ? Message::$bot : $by]); + } + + public static function CheckPings() { + foreach(Context::$onlineUsers as $user) { + if(gmdate("U") - $user->ping > Utils::$chat["MAX_IDLE_TIME"]) { + $user->sock->close(); + Context::Leave($user); + } + } + } + + public static function DoesSockExist($sock) { + foreach(Context::$onlineUsers as $u) { + if($u->sock == $sock) return true; + } + + return false; + } + + public static function Leave($user, $type = LEAVE_NORMAL) { + if(Context::GetChannel($user->channel)->channelType == CHANNEL_TEMP && Context::GetChannel($user->channel)->GetOwner()->id == $user->id) + Context::DeleteChannel($user->channel); + + Database::Logout($user); + Message::HandleLeave($user, $type); + unset(Context::GetChannel($user->channel)->users[$user->id]); + unset(Context::$onlineUsers[$user->id]); + } +} \ No newline at end of file diff --git a/lib/db.php b/lib/db.php new file mode 100644 index 0000000..c810ce6 --- /dev/null +++ b/lib/db.php @@ -0,0 +1,333 @@ +prepare(Database::$statements[$stmt]["query"]); + foreach(Database::$statements[$stmt] as $param => $value) { + if($param != "query") $tmp->bindValue(":{$param}", $value); + } + $tmp->execute(); + $ret = []; + if ($fetch) $ret = $tmp->fetchAll(PDO::FETCH_BOTH); + $tmp = null; + Database::$conn = null; + return $ret; + } + + public static function SpawnConnection() { + $chat = $GLOBALS["chat"]; + Database::$conn = new PDO($chat["DB_DSN"], $chat["DB_USER"], $chat["DB_PASS"], Database::$persist ? [PDO::ATTR_PERSISTENT => true] : []); + } + + public static function Init() { + $chat = $GLOBALS["chat"]; + Database::$useFlatFile = !$chat["DB_ENABLE"]; + $persist = $chat["DB_PERSIST"]; + if($chat["DB_ENABLE"]) { + try { + self::$persist = $persist; + Database::SpawnConnection(); + $pre = $chat["DB_TABLE_PREFIX"]; + + Database::$statements = [ + "logstore" => [ + "query" => "INSERT INTO {$pre}_logs (epoch, userid, username, color, channel, chrank, message, flags) VALUES (:epoch, :uid, :uname, :color, :chan, :chrank, :msg, :flags)", + "epoch" => "","uid" => "", "uname" => "", "color" => "", "chan" => "", "chrank" => "", "msg" => "", "flags" => "" + ], + "fetchbacklog" => [ + "query" => "SELECT * FROM {$pre}_logs WHERE channel = :chan OR channel = '@all' ORDER BY epoch DESC LIMIT 0, ". Backlog::$loglen, + "chan" => "" + ], + "login" => [ + "query" => "INSERT INTO {$pre}_online_users (userid, username, color, perms) VALUES (:uid, :uname, :col, :perms)", + "uid" => "", "uname" => "", "col" => "", "perms" => "" + ], + "logout" => [ + "query" => "DELETE FROM {$pre}_online_users WHERE userid = :uid", + "uid" => "" + ], + "clrusers" => [ + "query" => "TRUNCATE TABLE {$pre}_online_users" + ], + "crchan" => [ + "query" => "INSERT INTO {$pre}_channels (chname, pwd, priv) VALUES (:chn, :pwd, :priv)", + "chn" => "", "pwd" => "", "priv" => "" + ], + "modchan" => [ + "query" => "UPDATE {$pre}_channels SET chname = :chn, pwd = :pwd, priv = :priv WHERE chname = :chon", + "chn" => "", "pwd" => "", "priv" => "", "chon" => "" + ], + "delchan" => [ + "query" => "DELETE FROM {$pre}_channels WHERE chname = :chn", + "chn" => "" + ], + "fetchchan" => [ + "query" => "SELECT * FROM {$pre}_channels" + ], + "banuser" => [ + "query" => "INSERT INTO {$pre}_banned_users (ip, uid, username, expiration) VALUES (:ip, :id, :uname, :exp)", + "ip" => "", "id" => "", "uname" => "", "exp" => "" + ], + "unban" => [ + "query" => "DELETE FROM {$pre}_banned_users WHERE (ip IS NOT NULL AND ip LIKE :ip) OR (uid IS NOT NULL AND uid = :id) OR (username IS NOT NULL AND username = :uname)", + "ip" => "", "id" => "", "uname" => "" + ], + "fetchbans" => [ + "query" => "SELECT * FROM {$pre}_banned_users" + ], + "updatebans" => [ + "query" => "DELETE FROM {$pre}_banned_users WHERE expiration <= :epoch AND expiration != -1", + "epoch" => "" + ] + ]; + } catch(\Exception $err) { + echo "Could not connect to the database! Details: ". $err->getMessage() ."\n"; + return; + } + } else FFDB::Init(); + } + + public static function FetchBacklog($chan) { + if(!Database::$useFlatFile) { + $ret = new Backlog(); + + Database::$statements["fetchbacklog"]["chan"] = $chan; + $logs = Database::Execute("fetchbacklog", true); + foreach($logs as $log) + $ret->Log(new User($log["userid"], "", $log["username"], $log["color"], "", null), $log["message"], "rlbl", $log["epoch"], $log["flags"]); + $ret->logs = array_reverse($ret->logs); + + return $ret; + } else return new Backlog(); + } + + public static function TruncateUserList() { + if(!Database::$useFlatFile) Database::Execute("clrusers"); + } + + public static function Login($user) { + if(!Database::$useFlatFile) { + Database::$statements["login"]["uid"] = $user->id; + Database::$statements["login"]["uname"] = $user->username; + Database::$statements["login"]["col"] = $user->color; + Database::$statements["login"]["perms"] = $user->permstr; + Database::Execute("login"); + } + } + + public static function Logout($user) { + if(!Database::$useFlatFile) { + Database::$statements["logout"]["uid"] = $user->id; + Database::Execute("logout"); + } + } + + public static function Log($time, $user, $msg, $chan = null, $flags = "10010") { + $chan == null ? $user->channel : $chan; + if($chan == $GLOBALS["chat"]["DEFAULT_CHANNEL"]) $chan = "@default"; + + if(Database::$useFlatFile) + FFDB::Log("(". date("m/d/Y H:i:s") . ") ". $user->username ." to ". ($chan == null ? $user->channel : $chan) .": ". $msg); + else { + Database::$statements["logstore"]["epoch"] = $time; + Database::$statements["logstore"]["uid"] = $user->id; + Database::$statements["logstore"]["uname"] = $user->username; + Database::$statements["logstore"]["color"] = $user->color; + Database::$statements["logstore"]["chan"] = $chan == null ? $user->channel : $chan; + Database::$statements["logstore"]["chrank"] = $chan[0] == "@" ? 0 : Context::GetChannel($user->channel)->permissionLevel; + Database::$statements["logstore"]["msg"] = $msg; + Database::$statements["logstore"]["flags"] = substr($flags, 0, 4) ."0"; + Database::Execute("logstore"); + } + } + + public static function Ban($ip, $id, $username, $expire) { + if(Database::$useFlatFile) + FFDB::Ban($ip, $id, $username, $expire); + else { + Database::$statements["banuser"]["ip"] = $ip; + Database::$statements["banuser"]["id"] = $id; + Database::$statements["banuser"]["uname"] = $username; + Database::$statements["banuser"]["exp"] = $expire; + Database::Execute("banuser"); + } + } + + // you've got no bans + // you've got no drans + public static function GetAllBans() { + if(Database::$useFlatFile) + return FFDB::GetAllBans(); + else { + $time = time(); + Database::$statements["updatebans"]["epoch"] = $time; + Database::Execute("updatebans"); + + $blist = []; + $bans = Database::Execute("fetchbans", true); + foreach($bans as $ban) { + if($ban["expiration"] > $time || $ban["expiration"] == "-1") array_push($blist, new Ban($ban["ip"], $ban["uid"], $ban["username"], $ban["expiration"])); + } + return $blist; + } + } + + // you want some + // i'll give it ya + public static function Unban($ip, $id, $username) { + if(Database::$useFlatFile) + FFDB::Unban($ip, $id, $username); + else { + Database::$statements["unban"]["ip"] = $ip; + Database::$statements["unban"]["id"] = $id; + Database::$statements["unban"]["uname"] = $username; + Database::Execute("unban"); + } + } + + public static function CreateChannel($name, $pwd, $priv = 0) { + if(Database::$useFlatFile) + FFDB::CreateChannel($name, $pwd, $priv); + else { + Database::$statements["crchan"]["chn"] = $name; + Database::$statements["crchan"]["pwd"] = $pwd; + Database::$statements["crchan"]["priv"] = $priv; + Database::Execute("crchan"); + } + } + + public static function RemoveChannel($name) { + if(Database::$useFlatFile) + FFDB::RemoveChannel($name); + else { + Database::$statements["delchan"]["chn"] = $name; + Database::Execute("delchan"); + } + } + + public static function ModifyChannel($oldname, $newname, $pwd, $priv) { + if(Database::$useFlatFile) + FFDB::ModifyChannel($oldname, $newname, $pwd, $priv); + else { + Database::$statements["modchan"]["chon"] = $oldname; + Database::$statements["modchan"]["chn"] = $newname; + Database::$statements["modchan"]["pwd"] = $pwd; + Database::$statements["modchan"]["priv"] = $priv; + Database::Execute("modchan"); + } + } + + public static function GetAllChannels() { + if(Database::$useFlatFile) + return FFDB::GetAllChannels(); + else { + $clist = []; + $chans = Database::Execute("fetchchan", true); + foreach($chans as $chan) + $clist[$chan["chname"]] = new Channel($chan["chname"], $chan["pwd"], $chan["priv"], null, CHANNEL_PERM, Database::FetchBacklog($chan["chname"])); + return $clist; + } + } +} \ No newline at end of file diff --git a/lib/mods.php b/lib/mods.php new file mode 100644 index 0000000..d771c8e --- /dev/null +++ b/lib/mods.php @@ -0,0 +1,56 @@ +sock->send($msg); + } + + protected static function LogToAll($user, $msg, $flags) { + if(!Modules::ExecuteRoutine("OnMessageLog", [$user, &$msg, "@all", &$flags])) return; + foreach(Context::$channelList as $channel) + $channel->log->Log($user, $msg, Message::$msgId, null, $flags); + Database::Log(gmdate("U"), $user, $msg, "@all", $flags); + Modules::ExecuteRoutine("OnMessageLog", [$user, $msg, "@all", $flags]); + } + + protected static function SendToChannel($msg, $channel) { + if(is_string($channel)) { + if(Context::ChannelExists($channel)) { + $channel = Context::GetChannel($channel); + } else return; + } + + foreach($channel->users as $user) + $user->sock->send($msg); + } + + protected static function LogToChannel($user, $msg, $channel, $flags) { + if(is_string($channel)) { + if($channel[0] != "@") { + if (Context::ChannelExists($channel)) { + $channel = Context::GetChannel($channel); + } else return; + } + } + + Modules::ExecuteRoutine("OnMessageLog", [$user, &$msg, $channel, &$flags]); + if(!is_string($channel)) { + Database::Log(gmdate("U"), $user, $msg, $channel->name, $flags); + $channel->log->Log($user, $msg, Message::$msgId, null, $flags); + } else Database::Log(gmdate("U"), $user, $msg, $channel, $flags); + Modules::ExecuteRoutine("AfterMessageLog", [$user, $msg, $channel, $flags]); + } + + public static function BroadcastSilentMessage($user, $msg, $channel = ALL_CHANNELS, $msgid = null, $time = null, $alert = false, $flags = "1001") { + if(!is_string($channel)) $channel = $channel->name; + $msgid = $msgid == null ? Message::$msgId : $msgid; + $flags = substr($flags, 0, 4) ."0"; + if($channel == ALL_CHANNELS) + Message::SendToAll(Utils::PackMessage(P_CTX_DATA, ["1", $time == null ? gmdate("U") : $time, $user, $msg, $msgid, $alert == true ? "1": "0", $flags])); + else + Message::SendToChannel(Utils::PackMessage(P_CTX_DATA, ["1", $time == null ? gmdate("U") : $time, $user, $msg, $msgid, $alert == true ? "1": "0", $flags]), $channel); + } + + public static function BroadcastSilentBotMessage($type, $langid, $params, $channel = ALL_CHANNELS, $msgid = null, $time = null, $alert = false) { + Message::BroadcastSilentMessage(Message::$bot, Utils::FormatBotMessage($type, $langid, $params), $channel, $msgid, $time, $alert); + } + + public static function PrivateSilentMessage($user, $msg, $to, $msgid = null, $time = null, $alert = false, $flags = "1001", $pm = true) { + $msgid = $msgid == null ? Message::$msgId : $msgid; + $flags = substr($flags, 0, 4) . ($pm ? "1" : "0"); + $to->sock->send(Utils::PackMessage(P_CTX_DATA, ["1", $time == null ? gmdate("U") : $time, $user, $msg, $msgid, $alert == true ? "1": "0", $flags])); + } + + public static function PrivateSilentBotMessage($type, $langid, $params, $to, $msgid = null, $time = null, $alert = false) { + Message::PrivateSilentMessage(Message::$bot, Utils::FormatBotMessage($type, $langid, $params), $to, $msgid, $time, $alert, "1001", false); + } + + public static function ClearUserContext($user, $type = CLEAR_ALL) { + $user->sock->send(Utils::PackMessage(P_CTX_CLR, array($type))); + } + + public static function ClearUserContexts($channel = ALL_CHANNELS, $type = CLEAR_ALL) { + $out = Utils::PackMessage(P_CTX_CLR, array($type)); + + if($channel == ALL_CHANNELS) Message::SendToAll($out); + else Message::SendToChannel($out, ($channel == LOCAL_CHANNEL) ? Utils::$chat["DEFAULT_CHANNEL"] : $channel); + } + + // NOTE: DOES NOT SANITIZE INPUT MESSAGE !! DO THIS ELSEWHERE + public static function BroadcastUserMessage($user, $msg, $channel = LOCAL_CHANNEL, $flags = "1001") { + if(!is_string($channel)) $channel = $channel->name; + $flags = substr($flags, 0, 4) ."0"; + $out = Utils::PackMessage(P_SEND_MESSAGE, array(gmdate("U"), $user->id, $msg, Message::$msgId, $flags)); + + if($channel == ALL_CHANNELS) { + Message::SendToAll($out); + Message::LogToAll($user, $msg, $flags); + Message::$msgId++; + } else { + $channel = ($channel == LOCAL_CHANNEL) ? $user->channel : $channel; + + if(Context::ChannelExists($channel)) { + Message::SendToChannel($out, Context::GetChannel($channel)); + Message::LogToChannel($user, $msg, $channel, $flags); + Message::$msgId++; + } + } + } + + public static function BroadcastBotMessage($type, $langid, $params, $channel = ALL_CHANNELS) { + $msg = Utils::FormatBotMessage($type, $langid, $params); + $channel = ($channel == LOCAL_CHANNEL) ? Utils::$chat["DEFAULT_CHANNEL"] : $channel; + Message::BroadcastUserMessage(Message::$bot, $msg, $channel); + } + + public static function PrivateUserMessage($user, $to, $msg, $flags = "1001", $pm = true) { + $flags = substr($flags, 0, 4) . ($pm ? "1" : "0"); + $out = Utils::PackMessage(P_SEND_MESSAGE, array(gmdate("U"), $user->id, $msg, Message::$msgId, $flags)); + $to->sock->send($out); + if($user->id != $to->id) + Message::LogToChannel($user, "(@". $to->username .") ". $msg, "@priv", $flags); + Message::$msgId++; + } + + public static function PrivateBotMessage($type, $langid, $params, $to) { + $msg = Utils::FormatBotMessage($type, $langid, $params); + Message::PrivateUserMessage(Message::$bot, $to, $msg, "1001", false); + Message::$msgId++; + } + + public static function SendChannelToUser($user, $channel) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + if($user->getRank() >= $channel->permissionLevel) $user->sock->send(Utils::PackMessage(P_CHANNEL_INFO, ["0", $channel])); + } + + public static function SendAllChannelsToUser($user) { + $arr = []; + foreach(Context::$channelList as $channel) { + if($user->getRank() >= $channel->permissionLevel) array_push($arr, $channel); + } + $user->sock->send(Utils::PackMessage(P_CTX_DATA, ["2", count($arr), join(Utils::$separator, $arr)])); + } + + public static function HandleKick($user, $length = 0) { + if($length == 0) + $user->sock->send(Utils::PackMessage(P_BAKA, ["kick"])); + else + $user->sock->send(Utils::PackMessage(P_BAKA, ["ban", date("U") + $length])); + } + + public static function HandleUserModification($user) { + Message::SendToChannel(Utils::PackMessage(P_USER_CHANGE, [$user]), $user->channel); + } + + public static function HandleJoin($user) { + Message::SendToChannel(Utils::PackMessage(P_USER_JOIN, array(gmdate("U"), $user, Message::$msgId)), Utils::$chat["DEFAULT_CHANNEL"]); + + $user->sock->send(Utils::PackMessage(P_USER_JOIN, array("y", $user, Utils::$chat["DEFAULT_CHANNEL"]))); + Message::LogToChannel(Message::$bot, Utils::FormatBotMessage(MSG_NORMAL, "join", array($user->username)), Utils::$chat["DEFAULT_CHANNEL"], "10010"); + $user->sock->send(Utils::PackMessage(P_CTX_DATA, array("0", Context::GetChannel(Utils::$chat["DEFAULT_CHANNEL"])->GetAllUsers()))); + + $msgs = Context::GetChannel(Utils::$chat["DEFAULT_CHANNEL"])->log->GetAllLogStrings(); + foreach($msgs as $msg) + $user->sock->send(Utils::PackMessage(P_CTX_DATA, array("1", $msg))); + + Message::SendAllChannelsToUser($user); + + Message::$msgId++; + } + + public static function HandleChannelCreation($channel) { + foreach(Context::$onlineUsers as $user) + Message::SendChannelToUser($user, $channel); + } + + public static function HandleChannelDeletion($channel) { + if(is_string($channel)) $channel = Context::GetChannel($channel); + + foreach(Context::$onlineUsers as $user) { + if($user->getRank() >= $channel->permissionLevel) $user->sock->send(Utils::PackMessage(4, ["2", $channel->name])); + } + } + + public static function HandleChannelModification($channel, $oldname = "") { + if(is_string($channel)) $channel = Context::GetChannel($channel); + Database::ModifyChannel($oldname == "" ? $channel->name : $oldname, $channel->name, $channel->password, $channel->permissionLevel); + foreach(Context::$onlineUsers as $user) { + if($user->getRank() >= $channel->permissionLevel) { + $user->sock->send(Utils::PackMessage(4, ["1", $oldname == "" ? $channel->name : $oldname, $channel])); + if($user->channel == $oldname && $oldname != "") { + $user->sock->send(Utils::PackMessage(5, ["2", $channel->name])); + $user->channel = $channel->name; + } + } + } + } + + public static function HandleChannelSwitch($user, $to, $from) { + Message::SendToChannel(Utils::PackMessage(P_CHANGE_CHANNEL, array("1", $user->id, Message::$msgId)), $from); + Message::LogToChannel(Message::$bot, Utils::FormatBotMessage(MSG_NORMAL, "lchan", array($user->username)), $from, "10010"); + Message::SendToChannel(Utils::PackMessage(P_CHANGE_CHANNEL, array("0", $user, Message::$msgId)), $to); + Message::LogToChannel(Message::$bot, Utils::FormatBotMessage(MSG_NORMAL, "jchan", array($user->username)), $to, "10010"); + $user->sock->send(Utils::PackMessage(P_CTX_CLR, array(CLEAR_MSGNUSERS))); + $user->sock->send(Utils::PackMessage(P_CTX_DATA, array("0", Context::GetChannel($to)->GetAllUsers(), Message::$msgId))); + + $msgs = Context::GetChannel($to)->log->GetAllLogStrings(); + foreach($msgs as $msg) + $user->sock->send(Utils::PackMessage(P_CTX_DATA, array("1", $msg))); + + $user->sock->send(Utils::PackMessage(P_CHANGE_CHANNEL, array("2", $to))); + + Message::$msgId++; + } + + public static function HandleLeave($user, $method = LEAVE_NORMAL) { + Message::SendToChannel(Utils::PackMessage(P_USER_LEAVE, array($user->id, $user->username, $method, gmdate("U"), Message::$msgId)), $user->channel); + Message::LogToChannel(Message::$bot, Utils::FormatBotMessage(MSG_NORMAL, $method, array($user->username)), $user->channel, "10010"); + Message::$msgId++; + } + + public static function DeleteMessage($id) { + Message::SendToAll(Utils::PackMessage(P_MSG_DEL, [$id])); + } +} \ No newline at end of file diff --git a/lib/user.php b/lib/user.php new file mode 100644 index 0000000..7afe283 --- /dev/null +++ b/lib/user.php @@ -0,0 +1,79 @@ +id = $id; + $this->channel = $channel; + $this->originalData = [$username, $color, $permissions]; + $this->username = $username; + $this->color = $color; + $this->permstr = $permissions; + $this->permissions = explode("\f", $permissions); + $this->sock = $sock; + $this->ping = gmdate("U"); + } + + public function SetParameter($key, $value) { + $this->customParams[$key] = $value; + } + + public function GetParameter($key) { + if(array_key_exists($key, $this->customParams)) return $this->customParams[$key]; + else return null; + } + + public function GetOriginalUsername() { + return $this->originalData[0]; + } + + public function GetOriginalColor() { + return $this->originalData[1]; + } + + public function GetOriginalPermissionString() { + return $this->originalData[2]; + } + + public function Copy($user) { + $this->username = $user->username; + $this->color = $user->color; + $this->permstr = $user->permstr; + $this->permissions = $user->permissions; + } + + public function GetRank() { + return $this->permissions[0]; + } + + public function CanModerate() { + return $this->permissions[1] == "1"; + } + + public function CanViewLogs() { + return $this->permissions[2] == "1"; + } + + public function CanChangeNick() { + return $this->permissions[3] == "1"; + } + + public function ChannelCreationPermission() { + return $this->permissions[4]; + } + + public function __toString() { + return join(Utils::$separator, array($this->id, $this->username, $this->color, $this->permstr)); + } +} \ No newline at end of file diff --git a/lib/utils.php b/lib/utils.php new file mode 100644 index 0000000..f84c6c4 --- /dev/null +++ b/lib/utils.php @@ -0,0 +1,92 @@ +", "\n"], ["<", ">", "
"], $str); + } + + public static function SanitizeName($name) { + return str_replace([" ","\n","\t","\f"], ["_","","",""], htmlspecialchars($name, ENT_QUOTES)); + } + + public static function GetHeader($sock, $name) { + try { + return (string)$sock->WebSocket->request->getHeader($name, true); + } catch(\Exception $e) { + return ""; + } + } + + public static function DoesModExist($name) { + return file_exists("./mods/". $name); + } + + public static function DoesCommandExist($name) { + return file_exists("./commands/". $name .".php"); + } + + public static function Hash($in) { + return hash("sha256", $in); + } + + public static function IsValidIPAddress($addr) { + $addr = explode(".", $addr); + if(count($addr) != 4) return false; + foreach($addr as $subaddr) { + if(!is_numeric($subaddr) && $subaddr != "*") return false; + if(($subaddr > 255 || $subaddr < 0) && $subaddr != "*") return false; + } + return true; + } + + public static function CheckIPAddresses($addr1, $addr2) { + $addr1 = explode(".", $addr1); + $addr2 = explode(".", $addr2); + + for($i = 0; $i < 4; $i++) { + if($addr1[$i] != $addr2[$i] && $addr1[$i] != "*" && $addr2[$i] != "*") return false; + } + + return true; + } + + public static function CreateUniqueFile($dir) { + try { + while(file_exists($fname = "$dir/". md5(microtime()))); + } catch(\Exception $e) { + while(file_exists($fname = "$dir/". md5(time() + rand(0, 100)))); + } + return $fname; + } + + public static function Romanize($num) { + $lultima_romano = array("M" => 1000, "CM" => 900, "D" => 500, "CD" => 400, "C" => 100, "XC" => 90, + "L" => 50, "XL" => 40, "X" => 10, "IX" => 9, "V" => 5, "IV" => 4, "I" => 1); + + $piangera = ""; + while($num > 0) { + foreach($lultima_romano as $romano => $italia) { + if($num >= $italia) { + $num -= $italia; + $piangera .= $romano; + break; + } + } + } + + return $piangera; + } +} diff --git a/mods/Greeter/Greeter.php b/mods/Greeter/Greeter.php new file mode 100644 index 0000000..0e07fd1 --- /dev/null +++ b/mods/Greeter/Greeter.php @@ -0,0 +1,57 @@ +username ."!"], $user, "welcome"); + Message::PrivateSilentBotMessage(MSG_NORMAL, "say", ["To change your user color, use /color"], $user, "welcome"); + } + + /* + public static function AfterChannelCreate($channel) { + Message::BroadcastUserMessage(self::$bot, "channel ". $channel->name ." created", ALL_CHANNELS); + } + + public static function OnCommandReceive($user, &$cmd, &$args) { + //Message::BroadcastUserMessage(self::$bot, $user->username ." sent command ". $cmd ." with args ". implode(" ", $args), $user->channel); + } + + public static function AfterMessageReceived($user, $msg) { + if(strstr($msg, "nigger")) Message::BroadcastUserMessage(self::$bot, "the only nigger here is [i]you[/i]", $user->channel); + } + + public static function OnUserLeave($user) { + Message::BroadcastUserMessage(self::$bot, "this ". $user->username ." guy is a faggot", $user->channel); + } + */ +} \ No newline at end of file diff --git a/mods/core/core.php b/mods/core/core.php new file mode 100644 index 0000000..13da55d --- /dev/null +++ b/mods/core/core.php @@ -0,0 +1,426 @@ +size = $size; + if($copy != null) { + if($copy->Size() <= $this->size) $this->stack = $copy->Raw(); + else $this->stack = array_slice($copy->Raw(), $copy->Size()-$this->size); + } else $this->stack = []; + } + + public function Push($val, $id = -1) { + if($id == -1) + array_push($this->stack, $val); + else + $this->stack[$id] = $val; + if(count($this->stack) > $this->size) + $this->stack = array_slice($this->stack, 1); + } + + public function Pop() { + return array_pop($this->stack); + } + + public function Top() { + return ($this->stack == []) ? null : $this->stack[count($this->stack)-1]; + } + + public function Bottom() { + return ($this->stack == []) ? null : $this->stack[0]; + } + + public function MaxSize() { + return $this->size; + } + + public function Size() { + return count($this->stack); + } + + public function Full() { + return $this->size == count($this->stack); + } + + public function Raw() { + return $this->stack; + } +} + +class Main extends GenericMod { + protected static $silencedUsers; + protected static $allowedCmds = ["join", "whois", "afk"]; + + protected static $maxAfkTagLength = 5; + protected static $floodFilterSize = 30; + protected static $floodFilerDuration = 10; + + protected static $folder; + + protected static $logLength = 300; + protected static $messageLog; + + public static function Silence($user, $expires) { + self::$silencedUsers[Utils::$chat["AUTOID"] ? $user->GetOriginalUsername() : $user->id] = $expires; + $user->SetParameter("silence", $expires); + $fn = Utils::CreateUniqueFile(self::$folder ."/ffdb/silences"); + file_put_contents($fn, implode("\f", [$expires, $user->id, $user->GetOriginalUsername()])); + } + + public static function RemoveSilence($user) { + $search = Utils::$chat["AUTOID"] ? [$user->GetOriginalUsername(), 2] : [$user->id, 1]; + if(array_key_exists($search[0], self::$silencedUsers)) unset(self::$silencedUsers[$search[0]]); + $user->SetParameter("silence", null); + + $files = glob(self::$folder ."/ffdb/silences/*"); + foreach($files as $file) { + $parts = explode("\f", file_get_contents($file)); + if($parts[$search[1]] == $search[0]) { + unlink($file); + break; + } + } + } + + public static function IsSilencedFromList($user) { + $key = Utils::$chat["AUTOID"] ? $user->GetOriginalUsername() : $user->id; + if(array_key_exists($key, self::$silencedUsers)) { + if(self::$silencedUsers[$key] > gmdate("U") || self::$silencedUsers[$key] == "-1") return self::$silencedUsers[$key]; + else unset(self::$silencedUsers[$key]); + } + + return 0; + } + + public static function IsSilenced($user) { + $tmp = $user->GetParameter("silence"); + if($tmp == null) return false; + else { + if($tmp > gmdate("U") || $tmp == "-1") return true; + else { + self::RemoveSilence($user); + return false; + } + } + } + + public static function Init() { + $dir = self::$folder = self::GetModFolder(__NAMESPACE__); + + if(!file_exists("$dir/ffdb/silences")) mkdir("$dir/ffdb/silences", 0777, true); + self::$silencedUsers = []; + + $files = glob("$dir/ffdb/silences/*"); + foreach($files as $file) { + $parts = explode("\f", file_get_contents($file)); + + if(gmdate("U") >= $parts[0]) + unlink($file); + else + self::$silencedUsers[Utils::$chat["AUTOID"] ? $parts[2] : $parts[1]] = $parts[0]; + } + + self::$messageLog = new Stack(self::$logLength); + + self::AddCommandHook(["join", "create", "del", "pwd", "password", "priv", "privilege", "rank"], "handleChannelCommands"); + self::AddCommandHook(["kick", "ban", "pardon", "unban", "silence", "unsilence", "say", "whois", "ip", "delete", "unbanip", "pardonip", "bans", "banned"], "handleModeratorCommands"); + self::AddCommandHook(["whisper", "msg", "nick", "afk", "me", "action", "who", "color"], "handleUserCommands"); + } + + public static function OnUserJoin($user) { + if(($time = self::IsSilencedFromList($user)) != 0) $user->SetParameter("silence", $time); + } + + public static function OnMessageReceive($user, &$msg) { + if(($val = $user->GetParameter("afk")) != null) { + $user->username = mb_substr($user->username, mb_strlen("<$val>_")); + $user->SetParameter("afk", null); + Context::ModifyUser($user); + } + if(self::IsSilenced($user)) return false; + } + + public static function OnPacketReceive($conn, &$pid, &$data) { + if(($target = Context::GetUserBySock($conn)) != null) { + if($target->GetParameter("packetlog") == null) $target->SetParameter("packetlog", new Stack(self::$floodFilterSize)); + $target->GetParameter("packetlog")->Push(gmdate("U")); + $stack = $target->GetParameter("packetlog"); + //var_dump($stack); + $tmp = new Stack(self::$floodFilterSize-5, $stack); + if($tmp->Full()) { + if($stack->Top() - $stack->Bottom() <= self::$floodFilerDuration) + Message::PrivateBotMessage(MSG_NORMAL, "flwarn", [], $target); + } + + if($stack->Full()) { + if($stack->Top() - $stack->Bottom() <= self::$floodFilerDuration) + Context::KickUser($target, null, 0, false, "flood"); + } + } + } + + public static function AfterMessageLog($user, $msg, $channel, $flags) { + self::$messageLog->Push($user->id == -1 ? 999999999 : $user->GetRank(), Message::$msgId); + } + + public static function handleChannelCommands($cmd, $user, $args) { + switch($cmd) { + case "join": + if(isset($args[0]) && $args[0] != "") { + $pwd = isset($args[1]) ? Utils::Hash(implode(" ", array_slice($args, 1))) : ""; + Context::SwitchChannel($user, $args[0], $pwd); + } + break; + + case "create": + if($user->channelCreationPermission() != "0") { + if(isset($args[0]) && $args[0] != "") { + $channel = null; + if(is_numeric($args[0]) && isset($args[1]) && $args[1] != "") { + $args[0] = ($args[0] > $user->getRank()) ? $user->getRank() : $args[0]; + $channel = new Channel(implode("_", array_slice($args, 1)), "", $args[0], $user); + } else { + $channel = new Channel(implode("_", $args)); + $channel->channelOwner = $user; + } + + $channel->channelType = ($user->channelCreationPermission() == 1) ? CHANNEL_TEMP : CHANNEL_PERM; + + if(($ret = Context::CreateChannel($channel)) == "OK") { + if($channel->channelType == CHANNEL_TEMP) Context::SwitchChannel($user, $channel->name, $channel->password); + Message::PrivateBotMessage(MSG_NORMAL, "crchan", [$channel->name], $user); + } else + Message::PrivateUserMessage(Message::$bot, $user, $ret, "1001", false); + } else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/create"], $user); + break; + case "del": + if(isset($args[0]) && $args[0] != "") { + $name = implode($args, "_"); + if(($channel = Context::GetChannel($name)) != null) { + if($user->canModerate() || $channel->GetOwner()->id == $user->id) { + Context::DeleteChannel($channel); + Message::PrivateBotMessage(MSG_NORMAL, "delchan", [$name], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "ndchan", [$name], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "nochan", [$name], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user); + break; + + case "password": + case "pwd": + if($user->canModerate() || Context::GetChannel($user->channel)->GetOwner()->id == $user->id) { + Context::ChangeChannelPassword($user->channel, isset($args[0]) ? implode(" ", $args) : ""); + Message::PrivateBotMessage(MSG_NORMAL, "cpwdchan", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/pwd"], $user); + break; + + case "privilege": + case "rank": + case "priv": + if($user->canModerate() || Context::GetChannel($user->channel)->GetOwner()->id == $user->id) { + if(!isset($args[0]) || $args[0] <= $user->getRank()) { + Context::ChangeChannelPermission($user->channel, isset($args[0]) ? $args[0] : 0); + Message::PrivateBotMessage(MSG_NORMAL, "cprivchan", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "rankerr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/priv"], $user); + break; + } + } + + public static function handleModeratorCommands($cmd, $user, $args) { + if($user->canModerate()) { + switch ($cmd) { + case "kick": + if(($target = Context::GetUserByName($args[0])) != null) { + if($target->getRank() < $user->getRank() && strtolower($args[0]) != strtolower($user->username)) { + $length = (!isset($args[1]) || !is_numeric($args[1])) ? 0 : ($args[1] > 0 ? $args[1]*60 : -1); + Context::KickUser($target, $user, $length); + } else Message::PrivateBotMessage(MSG_ERROR, "kickna", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + break; + + case "ban": + if(($target = Context::GetUserByName($args[0])) != null) { + if($target->getRank() < $user->getRank() && strtolower($args[0]) != strtolower($user->username)) { + $length = (!isset($args[1]) || !is_numeric($args[1])) ? -1 : ($args[1] > 0 ? $args[1]*60 : -1); + Context::KickUser($target, $user, $length, true); + } else Message::PrivateBotMessage(MSG_ERROR, "kickna", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + break; + + case "pardon": + case "unban": + if(Context::Unban(null, null, $args[0], $user)) { + Message::PrivateBotMessage(MSG_NORMAL, "unban", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "notban", [$args[0]], $user); + break; + + case "pardonip": + case "unbanip": + if(Context::Unban(null, $args[0], null, $user)) { + Message::PrivateBotMessage(MSG_NORMAL, "unban", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "notban", [$args[0]], $user); + break; + + case "bans": + case "banned": + $list = []; + foreach(Context::$bannedUsers as $bid => $ban) { + if($ban->username == null) + $push = "". $ban->ip .""; + else + $push = "". $ban->username .""; + array_push($list, $push); + } + Message::PrivateBotMessage(MSG_NORMAL, "banlist", [implode(", ", $list)], $user); + break; + + case "silence": + if(($target = Context::GetUserByName($args[0])) != null) { + if($target->id != $user->id) { + if($target->getRank() < $user->getRank()) { + if (!self::IsSilenced($target)) { + $exp = isset($args[1]) ? (int)gmdate("U") + abs($args[1]*60) : -1; + self::Silence($target, $exp); + Message::PrivateBotMessage(MSG_NORMAL, "silence", [], $target); + Message::PrivateBotMessage(MSG_NORMAL, "silok", [$target->username], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "silerr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "silperr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "silself", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + break; + case "unsilence": + if(($target = Context::GetUserByName($args[0])) != null) { + if($target->getRank() < $user->getRank()) { + if (self::IsSilenced($target)) { + self::RemoveSilence($target); + Message::PrivateBotMessage(MSG_NORMAL, "unsil", [], $target); + Message::PrivateBotMessage(MSG_NORMAL, "usilok", [$target->username], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usilerr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usilperr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + break; + + case "say": + Message::BroadcastBotMessage(MSG_NORMAL, "say", [implode(" ", $args)]); + break; + + case "ip": + case "whois": + if(($tgt = Context::GetUserByName($args[0])) != null) { + Message::PrivateBotMessage(MSG_NORMAL, "ipaddr", [$args[0], $tgt->sock->remoteAddress], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + break; + + case "delete": + if(isset($args[0])) { + if(array_key_exists($args[0], self::$messageLog->Raw())) { + if(self::$messageLog->Raw()[$args[0]] <= $user->GetRank()) { + Message::DeleteMessage($args[0]); + } else Message::PrivateBotMessage(MSG_ERROR, "delerr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "delerr", [], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user); + break; + } + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/{$cmd}"], $user); + } + + public static function handleUserCommands($cmd, $user, $args) { + switch($cmd) { + case "nick": + if($user->canChangeNick()) { + $name = "~". trim(Utils::SanitizeName(mb_substr(join("_", $args), 0, Utils::$chat["MAX_USERNAME_LEN"]-1))); + if(!isset($args[0])) $name = $user->GetOriginalUsername(); + if(Context::GetUserByName($name) == null) { + Message::BroadcastBotMessage(MSG_NORMAL, "nick", [$user->username, $name], $user->channel); + $user->username = $name; + Context::ModifyUser($user); + } else Message::PrivateBotMessage(MSG_ERROR, "nameinuse", [$name], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/nick"], $user); + break; + case "whisper": + case "msg": + if(isset($args[0]) && isset($args[1])) { + if(($target = Context::GetUserByName($args[0])) != null) { + if($target->id != $user->id) { + $msg = implode(" ", array_slice($args, 1)); + Message::PrivateUserMessage($user, $target, $msg); + Message::PrivateUserMessage($user, $user, $target->username . " " . $msg); + } + } else Message::PrivateBotMessage(MSG_ERROR, "usernf", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user); + break; + case "afk": + $val = isset($args[0]) ? strtoupper(mb_substr($args[0], 0, self::$maxAfkTagLength)) : "AFK"; + if($user->GetParameter("afk") == null && $val != "") { + $user->SetParameter("afk", $val); + $user->username = "<$val>_". $user->username; + Context::ModifyUser($user); + } + break; + case "action": + case "me": + $msg = join(" ", $args); + if(trim($msg) != "") + Message::BroadcastUserMessage($user, "". $msg ."", LOCAL_CHANNEL, "1100"); + break; + case "who": + if(isset($args[0])) { + if(($chan = Context::GetChannel($args[0])) != null) { + if($chan->permissionLevel <= $user->GetRank() && ($chan->password != "" || $user->CanModerate())) { + $arr = []; + foreach ($chan->users as $u) + array_push($arr, "id == $user->id ? " style='font-weight: bold;'" : "") .">" . $u->username . ""); + Message::PrivateBotMessage(MSG_NORMAL, "whochan", [$args[0], implode(", ", $arr)], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "whoerr", [$args[0]], $user); + } else Message::PrivateBotMessage(MSG_ERROR, "nochan", [$args[0]], $user); + } else { + $arr = []; + foreach(Context::$onlineUsers as $u) + array_push($arr, "id == $user->id ? " style='font-weight: bold;'" : "") .">". $u->username .""); + Message::PrivateBotMessage(MSG_NORMAL, "who", [implode(", ", $arr)], $user); + } + break; + case "color": + if(isset($args[0])) { + $user->color = $args[0];//str_replace(["&",";","\"","'"], ["","","",""], $args[0]); + Context::ModifyUser($user); + } else Message::PrivateBotMessage(MSG_ERROR, "cmderr", [], $user); + break; + } + } + + public static function OnCommandReceive($user, &$cmd, &$args) { + /*if(!self::IsSilenced($user) || (self::IsSilenced($user) && in_array($cmd, self::$allowedCmds))) { + if($cmd == "afk") { + $val = isset($args[0]) ? strtoupper(mb_substr($args[0], 0, self::$maxAfkTagLength)) : "AFK"; + if($user->GetParameter("afk") == null && $val != "") { + $user->SetParameter("afk", $val); + $user->username = "<$val>_". $user->username; + Context::ModifyUser($user); + } + return false; + } else if($cmd == "silence") { + if($user->canModerate()) { + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/silence"], $user); + return false; + } else if($cmd == "unsilence") { + if($user->canModerate()) { + } else Message::PrivateBotMessage(MSG_ERROR, "cmdna", ["/unsilence"], $user); + return false; + } + } else return false;*/ + } +} diff --git a/mods/generic_mod.php b/mods/generic_mod.php new file mode 100644 index 0000000..8b282ba --- /dev/null +++ b/mods/generic_mod.php @@ -0,0 +1,80 @@ + $func) + array_push($retval, $cmd); + + return $retval; + } +} \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..533e161 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + level: 5 + paths: + - lib + - mods + bootstrapFiles: + - server.php diff --git a/server.php b/server.php new file mode 100644 index 0000000..af03d15 --- /dev/null +++ b/server.php @@ -0,0 +1,159 @@ + new Channel(Utils::SanitizeName(Utils::$chat["DEFAULT_CHANNEL"]), "", 0, null, CHANNEL_PERM, Database::FetchBacklog(DEFAULT_CHANNEL))], Database::GetAllChannels()); + Context::$bannedUsers = Database::GetAllBans(); + + echo "Server started.\n"; + } + + public function onOpen(ConnectionInterface $conn) { + $conn->remoteAddress = $conn->WebSocket->request->getHeader('X-Real-IP') ?? $conn->remoteAddress; + Context::CheckPings(); + } + + public function onMessage(ConnectionInterface $conn, $msg) { + $conn->remoteAddress = $conn->WebSocket->request->getHeader('X-Real-IP') ?? $conn->remoteAddress; + Context::CheckPings(); + if(true) { + $parts = explode(Utils::$separator, $msg); + $id = $parts[0]; + $parts = array_slice($parts, 1); + + if(!Modules::ExecuteRoutine("OnPacketReceive", [$conn, &$id, &$parts])) return; + + switch($id) { + case 0: + if(($u = Context::GetUserByID($parts[0])) != null) { + $u->ping = gmdate("U"); + $conn->send(Utils::PackMessage(0, array("pong"))); + } + break; + case 1: + if(!Context::DoesSockExist($conn)) { + $arglist = ""; + for($i = 0; $i < count($parts); $i++) + $arglist .= "&arg". ($i+1) ."=". urlencode($parts[$i]); + $aparts = file_get_contents(Utils::$chat['CHATROOT'] ."/?view=auth". $arglist); + + if(substr($aparts, 0, 3) == "yes") { + $aparts = explode("\n", mb_substr($aparts, 3)); + $reason = Context::AllowUser($aparts[1], $conn); + if($reason === 0 || $reason === "userfail") { + if(($length = Context::CheckBan(Utils::$chat["AUTOID"] ? null : $aparts[0], $conn->remoteAddress, Utils::SanitizeName($aparts[1]))) === false) { + $id = 0; + if(Utils::$chat["AUTOID"]) { + for($i = 1;; $i++) { + if(Context::GetUserByID($i) == null) { + $id = "".$i; + break; + } + } + } else $id = $aparts[0]; + + if($reason === "userfail") + $aparts[1] = $aparts[1] ."_". Utils::Romanize(rand(100, 1999)); + + Context::Join(new User($id, Utils::$chat["DEFAULT_CHANNEL"], Utils::SanitizeName($aparts[1]), $aparts[2], $aparts[3], $conn)); + } else $conn->send(Utils::PackMessage(1, array("n", "joinfail", $length))); + } else $conn->send(Utils::PackMessage(1, array("n", $reason))); + } else $conn->send(Utils::PackMessage(1, array("n", "authfail"))); + } + break; + case 2: + if(($user = Context::GetUserByID($parts[0])) != null) { + if($user->sock == $conn) { + if(trim($parts[1]) != "") { + $parts[1] = mb_substr(trim($parts[1]), 0, Utils::$chat["MAX_MSG_LEN"]); + //$parts[1] = str_replace("feel", "grip", $parts[1]); + if(trim($parts[1])[0] != "/") { + $out = Utils::Sanitize($parts[1]); + if(!Modules::ExecuteRoutine("OnMessageReceive", [$user, &$out])) return; + Message::BroadcastUserMessage($user, $out); + Modules::ExecuteRoutine("AfterMessageReceived", [$user, $out]); + } else { + //Database::Log(gmdate("U"), $user, Utils::Sanitize(trim($parts[1]))); + + $parts[1] = mb_substr(trim($parts[1]), 1); + $cmdparts = explode(" ", $parts[1]); + $cmd = strtolower(str_replace(".","",$cmdparts[0])); + $cmdparts = array_slice($cmdparts, 1); + for($i = 0; $i < count($cmdparts); $i++) + $cmdparts[$i] = Utils::Sanitize(trim($cmdparts[$i])); + + if(!Modules::ExecuteRoutine("OnCommandReceive", [$user, &$cmd, &$cmdparts])) return; + if(Modules::ExecuteCommand($cmd, $user, $cmdparts)) + Modules::ExecuteRoutine("AfterCommandReceived", [$user, $cmd, $cmdparts]); + else + Message::PrivateBotMessage(MSG_ERROR, "nocmd", [strtolower($cmd)], $user); + } + } + } + } + break; + } + + Modules::ExecuteRoutine("AfterPacketReceived", [$conn, $id, $parts]); + } else + $conn->close(); + } + + public function onClose(ConnectionInterface $conn) { + $conn->remoteAddress = $conn->WebSocket->request->getHeader('X-Real-IP') ?? $conn->remoteAddress; + echo $conn->remoteAddress ." has disconnected\n"; + foreach(Context::$onlineUsers as $user) { + if($user->sock == $conn) { + echo "found user ". $user->username .", dropped\n"; + Context::Leave($user); + Modules::ExecuteRoutine("OnUserLeave", [$user]); + } + } + Context::CheckPings(); + } + + public function onError(ConnectionInterface $conn, \Exception $err) { + $conn->remoteAddress = $conn->WebSocket->request->getHeader('X-Real-IP') ?? $conn->remoteAddress; + Context::CheckPings(); + echo "Error on ". $conn->remoteAddress .": ". $err ."\n"; + } +} + +$server = IoServer::factory( + new HttpServer( + new WsServer( + new Chat() + ) + ), + $GLOBALS["chat"]["PORT"] +); + +$server->run();