diff --git a/composer.json b/composer.json index eb5ffb2..490c45f 100755 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require": { "php": "~8.3", - "ext-openswoole": "~20230831", + "ext-sodium": "~8.3", "mirzaev/minimal": "^2.2.0", "twig/twig": "^3.4" }, diff --git a/mirzaev/notchat/system/controllers/index.php b/mirzaev/notchat/system/controllers/index.php index 48930c0..02f4516 100755 --- a/mirzaev/notchat/system/controllers/index.php +++ b/mirzaev/notchat/system/controllers/index.php @@ -54,7 +54,7 @@ final class index extends core // Generating the reponse echo json_encode( [ - 'html' => $this->view->render('sections/servers.html', ['current' => server::read($parameters['server'], errors: $this->errors), 'servers' => server::all(100, errors: $this->errors) ?? []]), + 'html' => $this->view->render('sections/servers.html', ['current' => isset($parameters['server']) ? server::read($parameters['server'], errors: $this->errors) : null, 'servers' => server::all(100, errors: $this->errors) ?? []]), 'errors' => null ] ); diff --git a/mirzaev/notchat/system/controllers/server.php b/mirzaev/notchat/system/controllers/server.php index 21946a5..7f8c086 100755 --- a/mirzaev/notchat/system/controllers/server.php +++ b/mirzaev/notchat/system/controllers/server.php @@ -34,7 +34,7 @@ final class server extends core // POST // Create a file with server data - model::write($parameters['hash'], file_get_contents('php://input'), $this->errors); + model::write($parameters['domain'], file_get_contents('php://input'), $this->errors); // Initializing a response headers header('Content-Type: application/json'); @@ -59,4 +59,49 @@ final class server extends core flush(); } } + + /** + * Read the server + * + * API for server reading + * + * @param array $parameters Parameters of the request (POST + GET) + * + * @return void Generated JSON to the output buffer + */ + public function read(array $parameters = []): void + { + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + // POST + + // Read a file with server data + $server = json_decode(model::read(model::domain($parameters['server']), $this->errors), true, 8); + + // Remove protected parameters + unset($server['key']); + + // Initializing a response headers + header('Content-Type: application/json'); + header('Content-Encoding: none'); + header('X-Accel-Buffering: no'); + + // Initializing of the output buffer + ob_start(); + + // Generating the reponse + echo json_encode( + [ + 'server' => $server, + 'errors' => static::text($this->errors) + ] + ); + + // Initializing a response headers + header('Content-Length: ' . ob_get_length()); + + // Sending and deinitializing of the output buffer + ob_end_flush(); + flush(); + } + } } diff --git a/mirzaev/notchat/system/models/dns.php b/mirzaev/notchat/system/models/dns.php new file mode 100755 index 0000000..ba28aae --- /dev/null +++ b/mirzaev/notchat/system/models/dns.php @@ -0,0 +1,231 @@ + + */ +class dns extends core +{ + /** + * Path to DNS of storaged servers + */ + final public const DNS = core::STORAGE . DIRECTORY_SEPARATOR . 'servers' . DIRECTORY_SEPARATOR . 'dns.txt'; + + /** + * Read + * + * Find and read server data from DNS registry by one of the values + * + * @param string|null $domain Domain of the server + * @param string|null $ip IP-address of the server + * @param string|int|null $port Port of the server + * @param int $line Line number on which the search will be stopped + * @param array &$errors Buffer of errors + * + * @return array|null Found DNS record of the server + */ + public static function read(?string $domain = null, ?string $ip = null, ?string $port = null, int $line = 0, &$errors = []): ?array + { + try { + // Open file with DNS records + $dns = fopen(static::DNS, 'r'); + + while (($row = fgets($dns)) !== false) { + // Iterate over rows + + // Initializing values of the server data + $record = [$_domain, $_ip, $_port] = explode(' ', $row); + + // Incrementing the line read counter + ++$line; + + if ($domain === $_domain || $ip === $_ip || $port === $_port) { + // Server found + + // Close file with DNS + fclose($dns); + + // Exit (success) + return $record; + } + } + + // Close file with DNS + fclose($dns); + } catch (exception $e) { + // Write to buffer of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * Write + * + * Write server data to the end of DNS registry + * + * @param string $domain Domain of the server + * @param string $ip IP-address of the server + * @param string|int $port Port of the server + * @param array &$errors Buffer of errors + * + * @return void + */ + public static function write(string $domain, string $ip, string|int $port, &$errors = []): void + { + try { + // Initializing part the file buffer (rows before target) + $before = []; + + // Initializing part the file buffer (rows before target) + $after = []; + + if (file_exists(static::DNS) && filesize(static::DNS) > 0) { + // File exists and not empty + + // Initializing the status that the DNS record has been found + $found = false; + + // Open file with DNS records + $dns = fopen(static::DNS, 'r'); + + while (($row = fgets($dns)) !== false) { + // Iterate over rows + + // Initializing values of the server data + [$_domain] = explode(' ', $row); + + // Writing the row to the file buffer (except the target record) + if ($domain === $_domain) $found = true; + else ${$found ? 'after' : 'before'}[] = $row; + } + + // Close file with DNS records + fclose($dns); + } + + // Open file with DNS records + $dns = fopen(static::DNS, 'c'); + + if (flock($dns, LOCK_EX)) { + // File locked + + // Clear file + ftruncate($dns, 0); + + // Write a new record to the DNS registry + fwrite($dns, trim(implode("", $before)) . "\n$domain $ip $port\n" . trim(implode("", $after))); + + // Apply changes + fflush($dns); + + // Unlock file + flock($dns, LOCK_UN); + } + } catch (exception $e) { + // Write to buffer of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + } + + /** + * Domain + * + * Convert domain or IP-address to domain + * + * @param string $server Domain or IP-address of the server + * @param array &$errors Buffer of errors + * + * @return string|null Domain of the server + */ + public static function domain(string $server, &$errors = []): ?string + { + try { + if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) { + // IP-address + + // Exit (success) + return static::read(ip: $server, errors: $errors)['domain']; + } else { + // Domain (implied) + + // Exit (success) + return $server; + } + } catch (exception $e) { + // Write to buffer of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + + /** + * IP-address + * + * Convert domain or IP-address to IP-address + * + * @param string $server Domain or IP-address of the server + * @param array &$errors Buffer of errors + * + * @return string|null IP-address of the server + */ + public static function ip(string $server, &$errors = []): ?string + { + try { + if (preg_match('/^(https:\/\/)?\d+\..*\d\/?$/', $server) === 1) { + // IP-address + + // Exit (success) + return $server; + } else { + // Domain (implied) + + // Exit (success) + return static::read(domain: $server, errors: $errors)['ip']; + } + } catch (exception $e) { + // Write to buffer of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } +} diff --git a/mirzaev/notchat/system/models/server.php b/mirzaev/notchat/system/models/server.php index bd58ad0..3f927e3 100755 --- a/mirzaev/notchat/system/models/server.php +++ b/mirzaev/notchat/system/models/server.php @@ -14,7 +14,7 @@ use exception, /** * Core of models * - * @package mirzaev\notchat\controllers + * @package mirzaev\notchat\models * @author Arsen Mirzaev Tatyano-Muradovich */ class server extends core @@ -29,20 +29,72 @@ class server extends core * * Create the file with server settings * - * @param string $hash Name of the file (unique hash) + * @param string $domain Domain of the server (unique) * @param string $json Data of the server with JSON format - * @param array $errors Buffer of errors + * @param array &$errors Buffer of errors * * @return void */ - public static function write(string $hash, string $json = '', array &$errors = []): void + public static function write(string $domain, string $json = '', array &$errors = []): void { try { - if (strlen($hash) > 512) throw new exception('Hash cannot be longer than 512 characters'); + if (strlen($domain) > 32) throw new exception('Domain cannot be longer than 32 characters'); - $file = fopen(static::SERVERS . DIRECTORY_SEPARATOR . "$hash.json", "w"); - fwrite($file, $json); - fclose($file); + // Initializing of path to file + $path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json"; + + // Initializing of host parameter + $host = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR']; + + if (empty($host)) throw new exception('ты что дохуя фокусник блять'); + + // Initializing of data of server + $new = ['domain' => $domain, 'ip' => $host] + json_decode($json, true, 8); + + if (strlen($new['key']) > 512) throw new exception('Public key cannot be longer than 512 characters'); + + if (file_exists($path)) { + // File found + + // Open file with server data + $file = fopen($path, "r"); + + // Read server data + $old = json_decode(fread($file, filesize($path)), true, 8); + + // Close file with server data + fclose($file); + + if ($new['key'] === $old['key'] || time() - filectime($path) > 259200) { + // The keys match or the file has not been updated for more than 3 days + + // Open file with server data + $file = fopen($path, "w"); + + // Write server data + fwrite($file, json_encode($new)); + + // Close file with server data + fclose($file); + + // Write DNS record + dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors); + } else throw new exception('Public keys do not match'); + } else { + // File is not found + + // Open file with server data + $file = fopen($path, "w"); + + // Write server data + fwrite($file, json_encode($new)); + + // Close file with server data + fclose($file); + + // Write DNS record + dns::write(domain: $new['domain'], ip: $new['ip'], port: $new['port'], errors: $errors); + } } catch (exception $e) { // Write to buffer of errors $errors[] = [ @@ -54,6 +106,47 @@ class server extends core } } + /** + * Read + * + * Read JSON from file of server + * + * @param string $domain Domain of the server + * @param array &$errors Buffer of errors + * + * @return string|null JSON with data of the server + */ + public static function read(string $domain, &$errors = []): ?string + { + try { + // Initializing of path to file + $path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json"; + + // Open file with server data + $file = fopen($path, "r"); + + // Read server data + $server = fread($file, filesize($path)); + + // Close file with server data + fclose($file); + + // Exit (success) + return $server; + } catch (exception $e) { + // Write to buffer of errors + $errors[] = [ + 'text' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'stack' => $e->getTrace() + ]; + } + + // Exit (fail) + return null; + } + /** * Read all * @@ -62,9 +155,9 @@ class server extends core * @param int $amount Number of servers per page * @param int $page Page of list of servers * @param int $time Number of seconds since the file was last edited (86400 seconds is 1 day) - * @param array $errors Buffer of errors + * @param array &$errors Buffer of errors * - * @return ?array Array with JSON entries, if found + * @return array|null Array with JSON entries, if found */ public static function all(int $amount = 100, int $page = 1, int $time = 86400, array &$errors = []): ?array { @@ -85,11 +178,20 @@ class server extends core // Skipping unnecessary files if (--$skip > $amount) continue; + // Skipping system shortcuts if ($file->isDot()) continue; - if (time() - $file->getCTime() > $time && $file->isReadable()) { + if (time() - $file->getCTime() < $time && $file->isReadable()) { + // The file is actual (3 days by default) and writable + + // Open file with server data $server = $file->openFile('r'); + + // Write server data to the output buffer $buffer[] = json_decode($server->fread($file->getSize())); + + // Close file with server data + unset($file); } if (--$amount < 1) break; diff --git a/mirzaev/notchat/system/public/index.php b/mirzaev/notchat/system/public/index.php index ee41870..b7a975c 100755 --- a/mirzaev/notchat/system/public/index.php +++ b/mirzaev/notchat/system/public/index.php @@ -34,8 +34,9 @@ $router = new router; // Запись маршрутов $router->write('/', 'index', 'index'); +$router->write('/server/read/$server', 'server', 'read', 'POST'); $router->write('/servers', 'index', 'servers', 'POST'); -$router->write('/servers/connect/$hash', 'server', 'write', 'POST'); +$router->write('/servers/connect/$domain', 'server', 'write', 'POST'); // Инициализация ядра $core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false)); diff --git a/mirzaev/notchat/system/public/js/chats.js b/mirzaev/notchat/system/public/js/chats.js index c380f58..7a087ee 100755 --- a/mirzaev/notchat/system/public/js/chats.js +++ b/mirzaev/notchat/system/public/js/chats.js @@ -6,34 +6,78 @@ if (typeof window.chats !== "function") { // Initialize of the class in global namespace window.chats = class chats { /** - * Request - * - * @param {string} address - * @param {string} body - * @param {string} method POST, GET... - * @param {object} headers - * @param {string} type Format of response (json, text...) - * - * @return {Promise} + * Server + */ + static server = { + /** + * Select server + * + * @param {string} server Domain or IP-address of the server (from cache by default) + * + * @return {void} Into the document will be generated and injected an HTML-element + */ + select(server = localStorage.server_ip ?? localStorage.server_domain) { + if (typeof server === "string" && server.length > 0) { + if (core.servers instanceof HTMLElement) { + core.request(`/server/read/${server}`).then((json) => { + if ( + json.errors !== null && typeof json.errors === "object" && + json.errors.length > 0 + ) {} else { + document.querySelector('figcaption[data-server="ip"]').innerText = `${json.ip}:${json.port}`; + document.querySelector('figcaption[data-server="description"]').innerText = `${json.description}`; + } + }); + } + } + }, + }; + + /** + * Generators */ static generate = { - servers() { - core.request("/servers").then((json) => { + /** + * HTML-element with a server selection form + * + * @param {string} server Domain or IP-address of the server (from cache by default) + * + * @return {void} Into the document will be generated and injected an HTML-element + */ + servers(server = localStorage.server_ip ?? localStorage.server_domain) { + core.request( + "/servers", + typeof server === "string" && server.length > 0 + ? `server=${server}}` + : "", + ).then((json) => { if (core.servers instanceof HTMLElement) core.servers.remove(); - if (json.errors !== null && typeof json.errors === 'object' && json.errors.length > 0) {} - else { + if ( + json.errors !== null && typeof json.errors === "object" && + json.errors.length > 0 + ) {} else { const element = document.createElement("div"); core.header.after(element); element.outerHTML = json.html; - core.servers = document.body.querySelector("section[data-section='servers']"); + core.servers = document.body.querySelector( + "section[data-section='servers']", + ); } }); }, + + /** + * @param {string} + * + * @return {void} Into the document will be generated and injected an HTML-element + */ chat() { core.request("/chat").then((json) => { - if (json.errors !== null && typeof json.errors === 'object' && json.errors.length > 0) {} - else { + if ( + json.errors !== null && typeof json.errors === "object" && + json.errors.length > 0 + ) {} else { const element = document.createElement("div"); const position = core.main.children.length; element.style.setProperty("--position", position); diff --git a/mirzaev/notchat/system/public/server.php b/mirzaev/notchat/system/public/server.php deleted file mode 100755 index 75bf122..0000000 --- a/mirzaev/notchat/system/public/server.php +++ /dev/null @@ -1,43 +0,0 @@ -set([ - 'ssl_cert_file' => '/etc/letsencrypt/live/mirzaev.sexy/fullchain.pem', - 'ssl_key_file' => '/etc/letsencrypt/live/mirzaev.sexy/privkey.pem' -]); - -$server->on("Start", function (Server $server) { - echo "OpenSwoole WebSocket Server is started at http://127.0.0.1:2024\n"; -}); - -$server->on('Open', function (Server $server, Request $request) { - echo "connection open: {$request->fd}\n"; - - $server->tick(1000, function () use ($server, $request) { - $server->push($request->fd, json_encode(["hello", time()])); - }); -}); - -$server->on('Message', function (Server $server, Frame $frame) { - echo "received message: {$frame->data}\n"; - $server->push($frame->fd, json_encode(["hello", time()])); -}); - -$server->on('Close', function (Server $server, int $fd) { - echo "connection close: {$fd}\n"; -}); - -$server->on('Disconnect', function (Server $server, int $fd) { - echo "connection disconnect: {$fd}\n"; -}); - -$server->start(); diff --git a/mirzaev/notchat/system/public/themes/default/css/animations.css b/mirzaev/notchat/system/public/themes/default/css/animations.css index 5035e0c..e045bf3 100755 --- a/mirzaev/notchat/system/public/themes/default/css/animations.css +++ b/mirzaev/notchat/system/public/themes/default/css/animations.css @@ -17,5 +17,14 @@ animation-name: uprise; animation-fill-mode: forwards; animation-timing-function: ease-in; - +} + + +@keyframes marquee { + 0% { left: 0; } + 100% { left: -100%; } +} + +.animation.marquee { + animation: marquee var(--speed, 3s) linear infinite; } diff --git a/mirzaev/notchat/system/public/themes/default/css/fonts/hack.css b/mirzaev/notchat/system/public/themes/default/css/fonts/hack.css index 75cdac5..f9e3571 100755 --- a/mirzaev/notchat/system/public/themes/default/css/fonts/hack.css +++ b/mirzaev/notchat/system/public/themes/default/css/fonts/hack.css @@ -29,35 +29,3 @@ font-style: italic; font-display: swap; } - -@font-face { - font-family: 'Hack'; - src: url('/themes/default/fonts/hack/hack-regular-subset.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-regular-subset.woff?sha=3114f1256') format('woff'); - font-weight: 400; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Hack'; - src: url('/themes/default/fonts/hack/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-bold-subset.woff?sha=3114f1256') format('woff'); - font-weight: 700; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: 'Hack'; - src: url('/themes/default/fonts/hack/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-italic-subset.woff?sha=3114f1256') format('woff'); - font-weight: 400; - font-style: italic; - font-display: swap; -} - -@font-face { - font-family: 'Hack'; - src: url('/themes/default/fonts/hack/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-bolditalic-subset.woff?sha=3114f1256') format('woff'); - font-weight: 700; - font-style: italic; - font-display: swap; -} diff --git a/mirzaev/notchat/system/public/themes/default/css/main.css b/mirzaev/notchat/system/public/themes/default/css/main.css index 73022de..561a894 100755 --- a/mirzaev/notchat/system/public/themes/default/css/main.css +++ b/mirzaev/notchat/system/public/themes/default/css/main.css @@ -36,7 +36,7 @@ body { height: 100vh; display: grid; grid-template-columns: [header] 220px [settings] 320px [main] auto [footer] 180px; - grid-template-rows: [aside] var(--row-aside, 200px) [settings] var(--row-settings, 100px) [main] auto; + grid-template-rows: [aside] var(--row-aside, 200px) [settings] var(--row-settings, 100px) [main] calc(100vh - var(--row-aside) - var(--gap) - var(--row-settings) - var(--gap)) auto; gap: var(--gap, 16px); padding: 0; overflow-x: scroll; diff --git a/mirzaev/notchat/system/storage/servers/7ed5995b3d6b16779909ca68e2c4c8ddb05601cb26ef6c6c5c22bfa42a0763a8.json b/mirzaev/notchat/system/storage/servers/7ed5995b3d6b16779909ca68e2c4c8ddb05601cb26ef6c6c5c22bfa42a0763a8.json deleted file mode 100755 index a278ae3..0000000 --- a/mirzaev/notchat/system/storage/servers/7ed5995b3d6b16779909ca68e2c4c8ddb05601cb26ef6c6c5c22bfa42a0763a8.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"MIRZAEV","location":"Russia","description":"","accounts":[],"settings":{"chats":{"max":50,"private":false,"languages":[]}}} \ No newline at end of file diff --git a/mirzaev/notchat/system/views/themes/default/sections/servers.html b/mirzaev/notchat/system/views/themes/default/sections/servers.html index 121beb1..ae905fa 100755 --- a/mirzaev/notchat/system/views/themes/default/sections/servers.html +++ b/mirzaev/notchat/system/views/themes/default/sections/servers.html @@ -2,21 +2,22 @@ - - {% if current %} -

{{ current.name }} {{ current.location}}

-

{{ current.description }}

- {% endif %} + +
+
{% if current %}{{ current.ip }}:{{ current.port }}{% endif %}
+ {{ current.description }} +