Compare commits

..

No commits in common. "stable" and "0.2.0" have entirely different histories.

168 changed files with 130 additions and 3624 deletions

0
.gitignore vendored Executable file → Normal file
View File

2
asdasd

@ -1 +1 @@
Subproject commit 07b2c6ecfc929858d5905fa2159f5cba9c6228a1
Subproject commit c18318f5de0e34f6af1491f8e550ffd273c55450

View File

@ -24,7 +24,7 @@
},
"require": {
"php": "~8.3",
"ext-sodium": "~8.3",
"ext-openswoole": "~20230831",
"mirzaev/minimal": "^2.2.0",
"twig/twig": "^3.4"
},

49
mirzaev/notchat/system/controllers/core.php Executable file → Normal file
View File

@ -6,10 +6,7 @@ namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\views\templater,
mirzaev\notchat\models\core as models,
mirzaev\notchat\models\log,
mirzaev\notchat\models\enumerations\log as type,
mirzaev\notchat\models\firewall;
mirzaev\notchat\models\core as models;
// Framework for PHP
use mirzaev\minimal\controller;
@ -33,7 +30,7 @@ class core extends controller
protected array $errors = [];
/**
* Constructor
* Constructor of an instance
*
* @param bool $initialize Initialize a controller?
*
@ -47,51 +44,14 @@ class core extends controller
if ($initialize) {
// Initializing is requested
// Write to the log of connections
log::write(
type::CONNECTIONS,
trim("[{$_SERVER['REMOTE_ADDR']}] "
. (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ")
. (empty($_SERVER['HTTP_REFERER']) ? '' : "[{$_SERVER['HTTP_REFERER']}] ")
. (empty($_SERVER['HTTP_USER_AGENT']) ? '' : "[{$_SERVER['HTTP_USER_AGENT']}]"), ' ')
);
// Initializing of preprocessor of views
$this->view = new templater();
// Checking for ban
if (
(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && firewall::banned($_SERVER['HTTP_X_FORWARDED_FOR']))
|| (isset($_SERVER['REMOTE_ADDR']) && firewall::banned($_SERVER['REMOTE_ADDR']))
) {
// IP-address is banned
// Sending a reply
echo $this->view->render('pages/ban.html');
// Exit (success)
die;
}
// Initializing of models core
new models();
// Initializing a response headers
header('Service-Worker-Allowed: /');
// Initializing of preprocessor of views
$this->view = new templater();
}
}
/**
* Destructor
*
* @return void
*/
public function __destruct()
{
// Analyze recent requests
firewall::analyze();
}
/**
* Check of initialization
*
@ -108,4 +68,5 @@ class core extends controller
default => isset($this->{$name})
};
}
}

208
mirzaev/notchat/system/controllers/index.php Executable file → Normal file
View File

@ -5,10 +5,7 @@ declare(strict_types=1);
namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\controllers\core,
mirzaev\notchat\models\dns,
mirzaev\notchat\models\server,
mirzaev\notchat\models\text;
use mirzaev\notchat\controllers\core;
/**
* Index controller
@ -25,62 +22,6 @@ final class index extends core
*/
public function index(array $parameters = []): ?string
{
// Initializing a list with languages
$this->view->languages = text::list($this->errors);
// Инициализация бегущей строки
$this->view->hotline = [
'id' => 'hotline'
];
// Инициализация параметров бегущей строки
$this->view->hotline = [
'parameters' => [
'step' => '0.3'
]
] + $this->view->hotline;
// Инициализация аттрибутов бегущей строки
$this->view->hotline = [
'attributes' => []
] + $this->view->hotline;
// Инициализация элементов бегущей строки
$this->view->hotline = [
'elements' => [
['html' => ''],
[
'tag' => 'article',
'attributes' => [
'class' => 'trash'
],
'html' => $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'trash.html', [
'id' => 'trash_1',
'title' => 'Linoleum',
'main' => '<p>Do you really like the rotting smell, dull sound and disgusting greasy shine of parquet-like fake pattern on a polymer toxic film? <b>Are you fucking insane?</b></p>',
'image' => [
'src' => 'https://virus.mirzaev.sexy/images/trash/linoleum.png',
'alt' => 'Linoleum'
]
])
],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => ''],
['html' => '']
]
] + $this->view->hotline;
// Exit (success)
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render('chats.html');
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $this->view->render('chats.html');
@ -88,151 +29,4 @@ final class index extends core
// Exit (fail)
return null;
}
/**
* Render the servers section
*
* @param array $parameters Parameters of the request (POST + GET)
*
* @return void Generated JSON to the output buffer
*/
/* public function cache(array $parameters = []): void
{
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// GET
if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'manifest')) {
// File found
// Clearing the output buffer
if (ob_get_level()) ob_end_clean();
// Initializing of the output buffer
ob_start();
// Initializing a response headers
header('Content-Type: text/cache-manifest');
// Generating the reponse
if ($file = fopen($path, 'r')) {
// File open
// Reading file
while (!feof($file)) echo fread($path, 1024);
// Closing file
fclose($file);
}
// Initializing a response headers
header('Content-Length: ' . ob_get_length());
// Sending and deinitializing of the output buffer
ob_end_flush();
flush();
}
}
} */
/**
*
*
* @return void Generated JSON to the output buffer
*/
/* public function cache(array $parameters = []): void
{
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// GET
if (file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR . 'cache.js')) {
// File found
// Clearing the output buffer
if (ob_get_level()) ob_end_clean();
// Initializing of the output buffer
ob_start();
// Initializing a response headers
header('Content-Type: application/javascript charset=utf-8');
// Generating the reponse
if ($file = fopen($path, 'r')) {
// File open
// Reading file
while (!feof($file)) echo fread($file, 1024);
// Closing file
fclose($file);
}
// Initializing a response headers
header('Content-Length: ' . ob_get_length());
// Sending and deinitializing of the output buffer
ob_end_flush();
flush();
}
}
} */
/**
* Render the offline page
*/
public function offline(): ?string
{
// Initializing of the title
$this->view->title = 'bye';
// Exit (success)
return $this->view->render('pages/offline.html');
}
/**
* Render the servers section
*
* @param array $parameters Parameters of the request (POST + GET)
*
* @return void Generated JSON to the output buffer
*/
public function servers(array $parameters = []): void
{
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST
// 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(
[
'html' => $this->view->render(
'sections/servers.html',
[
'current' => isset($parameters['server'])
&& ($server = server::read(domain: dns::domain($parameters['server'], errors: $this->errors), errors: $this->errors))
? json_decode($server, true, 8)
: null,
'servers' => server::all(100, errors: $this->errors) ?? []
]
),
'status' => isset($server) ? 'connected' : 'disconnected',
'errors' => null
]
);
// Initializing a response headers
header('Content-Length: ' . ob_get_length());
// Sending and deinitializing of the output buffer
ob_end_flush();
flush();
}
}
}

View File

@ -1,140 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\controllers;
// Files of the project
use mirzaev\notchat\controllers\core,
mirzaev\notchat\controllers\traits\errors,
mirzaev\notchat\models\dns,
mirzaev\notchat\models\server as model,
mirzaev\notchat\models\log,
mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception;
/**
* Server controller
*
* @package mirzaev\notchat\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class server extends core
{
use errors;
/**
* Write the server
*
* API for server registration
*
* @param array $parameters Parameters of the request (POST + GET)
*
* @return void Generated JSON to the output buffer
*/
public function write(array $parameters = []): void
{
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// POST
// Create a file with server data
model::write(dns::domain($parameters['server'], errors: $this->errors), file_get_contents('php://input'), $this->errors);
// 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(
[
'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();
}
}
/**
* 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
// Initializing of buffer of response
$return = [];
try {
// Decode of user input
$parameters['server'] = urldecode($parameters['server']);
// Validation of user input
if (mb_strlen($parameters['server']) > 512) throw new exception('Server address longer than 512 characters');
if ($domain = dns::domain($parameters['server'], errors: $this->errors)) {
if ($raw = model::read(domain: $domain)) {
// File found and read
// Decoding server data to remove protected parameters
$return['server'] = json_decode($raw, true, 8);
// Remove protected parameters
unset($return['server']['key']);
} else throw new exception('Server offline');
} else throw new exception('Server not found');
} catch (exception $e) {
// Write to the buffer of errors
$this->errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// 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(
$return + [
'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();
}
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\controllers\traits;
/**
* Trait of handler of errors
*
* @package mirzaev\notchat\controllers\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait errors
{
private static function text(array $errors): array
{
// Initializing of output buffer
$buffer = [];
foreach ($errors as $offset => $error) {
// Iterating through errors
// Checking for nesting and writing to the output buffer (entry into recursion)
if (isset($error['text'])) $buffer[] = $error['text'];
else if (is_array($error) && count($error) > 0) $buffer[$offset] = static::text($error);
}
return $buffer;
}
}

14
mirzaev/notchat/system/models/core.php Executable file → Normal file
View File

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type;
// Framework for PHP
use mirzaev\minimal\model;
@ -26,16 +23,6 @@ class core extends model
*/
final public const POSTFIX = '';
/**
* Path to public directory
*/
final public const PUBLIC = '..' . DIRECTORY_SEPARATOR . 'public';
/**
* Path to storage directory
*/
final public const STORAGE = '..' . DIRECTORY_SEPARATOR . 'storage';
/**
* Constructor of an instance
*
@ -121,3 +108,4 @@ class core extends model
};
}
}

View File

@ -1,252 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception;
/**
* DNS registry
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
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, 'c+');
while (($row = fgets($dns, 512)) !== 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 || ($port && $ip === $_ip && $port === $_port) || (!$port && $ip === $_ip || $port === $_port)) {
// Server found (domain, ip, ip + port)
// Close file with DNS
fclose($dns);
// Exit (success)
return array_combine(['domain', 'ip', 'port'], $record);
}
}
// Close file with DNS
fclose($dns);
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// 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 = [];
// Initializing the status that the DNS record has been found
$found = false;
if (file_exists(static::DNS) && filesize(static::DNS) > 0) {
// File exists and not empty
// Open file with DNS records
$dns = fopen(static::DNS, 'c+');
while (($row = fgets($dns, 512)) !== 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 = $row;
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, (count($before) > 0 ? trim(implode("", $before)) . "\n" : '') . "$domain $ip $port" . (count($after) ? "\n" . trim(implode("", $after)) : ''));
// Apply changes
fflush($dns);
// Unlock file
flock($dns, LOCK_UN);
}
// Write to the log
log::write(type::DNS, $found ? "[UPDATE] $found -> $domain $ip $port" : "[CREATE] $domain $ip $port");
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
/**
* Domain
*
* Convert domain or IP-address to domain
*
* @param string $server Domain or IP-address of the server
* @param bool $strict Check for port compliance?
* @param array &$errors Buffer of errors
*
* @return string|null Domain of the server
*/
public static function domain(string $server, bool $strict = true, &$errors = []): ?string
{
try {
if (preg_match('/^(?:https:\/\/)?([\d\.]*)(?:$|:?(\d.*\d)?\/?$)/', $server, $matches) === 1) {
// IP-address
// Initializing of parts of address
@[, $ip, $port] = $matches;
// Exit (success)
return static::read(ip: $ip, port: $strict ? $port : null, errors: $errors)['domain'] ?? null;
} else {
// Domain (implied)
// Exit (success)
return $server ?? null;
}
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// 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.*\d)?\/?$/', $server, $matches) === 1) {
// IP-address
// Initializing of parts of address
[, $ip, $port] = $matches;
// Exit (success)
return $ip ?? null;
} else {
// Domain (implied)
// Exit (success)
return static::read(domain: $server, errors: $errors)['ip'] ?? null;
}
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models\enumerations;
/**
* Types of logs
*
* @package mirzaev\notchat\models\enumerations
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
enum log: string
{
case ERRORS = 'errors.log';
case CONNECTIONS = 'connections.log';
case BANS = 'bans.log';
case FIREWALL = 'firewall.log';
case SESSIONS = 'sessions.log';
case SERVERS = 'servers.log';
case DNS = 'dns.log';
}

View File

@ -1,211 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type,
mirzaev\notchat\models\traits\file,
mirzaev\notchat\models\traits\log as read;
// Built-in libraries
use exception,
datetime;
/**
* Firewall
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class firewall extends core
{
use file, read {
file::read as protected file;
read::ban as protected _ban;
}
/**
* Write
*
* @param int $range Time range for reading last connections (seconds)
* @param int $limit Limit on the number of requests in the allotted time
* @param array &$errors Buffer of errors
*
* @return void
*
* @todo
* 1. Dividing logs based on volume achieved
*/
public static function analyze(int $range = 10, int $limit = 20, &$errors = []): void
{
try {
// Initializing of path to the file with connections
$path = log::LOGS . DIRECTORY_SEPARATOR . type::CONNECTIONS->value;
if (file_exists($path)) {
// The file exists
// Initializing of current time
$current = new datetime();
// Initializing of target past time
$past = (clone $current)->modify("-$range seconds");
// Initializing of the buffer of IP-addresses found in the connections log [ip => amount of connections per $time]
$ips = [];
// Open file with connections
$connections = fopen($path, 'r');
foreach (static::file($connections, 300, 0, -1) as $row) {
// Reading a file backwards (rows from end)
// Skipping of empty rows
if (empty($row)) continue;
try {
// Deserializing a row
$parameters = static::connection($row, $errors);
if ($parameters !== null && is_array($parameters)) {
// Parameters have been initialized
// Initializing of parameters of connection
[, $date, $ip, $forwarded, $referer, $useragent] = $parameters;
// Initializing of date of connection
$date = DateTime::createFromFormat('Y.m.d H:i:s', $date);
if (0 <= $elapsed = $date->getTimestamp() - $past->getTimestamp()) {
// No more than $range seconds have passed since connection
// Initializing of counter of connections
$ips[$ip] ??= 0;
// Incrementing of counter of connections
++$ips[$ip];
}
}
} catch (exception $e) {
continue;
}
}
// Close file with connections
fclose($connections);
}
// Ban IP-addresses that do not meet the conditions
foreach ($ips ?? [] as $ip => $connections) if ($connections >= $limit) static::ban($ip, new datetime('+2 minutes'));
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
/**
* Ban
*
* @param string $ip IP-address
* @param datetime $end Date for unban
* @param array &$errors Buffer of errors
*
* @return void
*/
public static function ban(string $ip, datetime $end, &$errors = []): void
{
try {
// Write to the log of bans
log::write(type::BANS, "[{$end->format('Y.m.d H:i:s')}] [$ip]");
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
/**
* Check for ban
*
* Search in the ban log
*
* @param string $ip IP-address
* @param array &$errors Buffer of errors
*
* @return bool IP-address is banned? (null - has errors)
*
* @todo Count rows of file for reading instead of constant value (500 rows)
*/
public static function banned(string $ip, &$errors = []): ?bool
{
try {
// Initializing of path to the file with bans
$path = log::LOGS . DIRECTORY_SEPARATOR . type::BANS->value;
if (file_exists($path)) {
// The file exists
// Open file with bans
$bans = fopen($path, 'r');
foreach (static::file($bans, 500, 0, -1) as $row) {
// Reading a file backwards (rows from end)
// Skipping of empty rows
if (empty($row)) continue;
try {
// Deserializing a row
$parameters = static::_ban($row, $errors);
if ($parameters !== null && is_array($parameters)) {
// Parameters have been initialized
// Initializing of parameters of connection
[, $from, $to, $_ip] = static::_ban($row, $errors);
// Check for ban and exit (success)
if ($ip === $_ip && (new datetime)->getTimestamp() - DateTime::createFromFormat('Y.m.d H:i:s', $to)->getTimestamp() < 0) return true;
}
} catch (exception $e) {
continue;
}
}
}
// Exit (success)
return false;
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -1,72 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception;
/**
* Log
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class log extends core
{
/**
* Path to DNS of storaged servers
*/
final public const LOGS = core::STORAGE . DIRECTORY_SEPARATOR . 'logs';
/**
* Write
*
* @param type $type Type of log
* @param string $value Text to write
* @param array &$errors Buffer of errors
*
* @return void
*
* @todo
* 1. Dividing magazines based on volume achieved
*/
public static function write(type $type, string $value, &$errors = []): void
{
try {
// Initializing of path to the file of the log
$path = static::LOGS . DIRECTORY_SEPARATOR . $type->value;
// Open file of the log
$log = fopen($path, 'a');
if (flock($log, LOCK_EX)) {
// File locked
// Initializing of date
$date = date_format(date_create(), 'Y.m.d H:i:s');
// Write to the log
fwrite($log, (filesize($path) === 0 ? '' : PHP_EOL) . "[$date] $value");
// Apply changes
fflush($log);
// Unlock file
flock($log, LOCK_UN);
}
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
}

View File

@ -1,247 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception,
DirectoryIterator as parser;
/**
* Server
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class server extends core
{
/**
* Path to storage of servers
*/
final public const SERVERS = core::STORAGE . DIRECTORY_SEPARATOR . 'servers';
/**
* Write
*
* Create the file with server settings
*
* @param string $domain Domain of the server (unique)
* @param string $json Data of the server with JSON format
* @param array &$errors Buffer of errors
*
* @return void
*/
public static function write(string $domain, string $json = '', array &$errors = []): void
{
try {
//
if (strlen($domain) > 32) throw new exception('Domain cannot be longer than 32 characters');
// 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, "c+");
// 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, "c");
// 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);
// Write to the log of servers
log::write(type::SERVERS, "[UPDATE] {$old['domain']} {$old['ip']}:{$old['port']} -> {$new['domain']} {$new['ip']}:{$new['port']}");
} else throw new exception('Public keys do not match');
} else {
// File is not found
// Open file with server data
$file = fopen($path, "c");
// 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);
// Write to the log of errors
log::write(type::SERVERS, "[CREATE] {$new['domain']} {$new['ip']}:{$new['port']}");
}
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
}
/**
* Read
*
* Read JSON from file of server
*
* @param string $domain Domain of the server
* @param int $time Number of seconds since the file was last edited (86400 seconds is 1 day)
* @param array &$errors Buffer of errors
*
* @return string|null JSON with data of the server
*/
public static function read(string $domain, int $time = 86400, &$errors = []): ?string
{
try {
// Initializing of path to file
$path = static::SERVERS . DIRECTORY_SEPARATOR . "$domain.json";
if (file_exists($path) && filesize($path) > 0) {
// File exists and not empty
if (time() - filectime($path) < $time && is_readable($path)) {
// The file is actual (1 day by default) and writable
// Open file with server data
$file = fopen($path, 'c+');
// Read server data
$server = fread($file, filesize($path));
// Close file with server data
fclose($file);
// Exit (success)
return $server;
}
}
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
/**
* Read all
*
* Read JSON from all files of servers
*
* @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
*
* @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
{
try {
// Initializing of the output buffer
$buffer = [];
// Initializing the minimum value of amount
if ($amount < 1) $amount = 1;
// Initializing the minimum value of page
if ($page < 1) $page = 1;
// Initializing of amount to skip
$skip = $page * $amount;
foreach (new parser(static::SERVERS) as $file) {
// Iterate through all files in a directory
// Skipping unnecessary files
if (--$skip > $amount) continue;
// Skipping system shortcuts
if ($file->isDot()) continue;
if (time() - $file->getCTime() < $time && $file->isReadable()) {
// The file is actual (1 day by default) and readable
if (($size = $file->getSize()) > 0) {
// The file is not empty
// Open the file with server data
$server = $file->openFile('c+');
// Write server data to the output buffer
$buffer[] = json_decode($server->fread($size));
// Close the file with server data
unset($file);
}
}
// Exit (success)
if (--$amount < 1) break;
}
// Exit (success)
return $buffer;
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -1,126 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models;
// Files of the project
use mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception,
DirectoryIterator as parser;
/**
* Text
*
* @package mirzaev\notchat\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class text extends core
{
/**
* Path to the directory with translation files (json)
*/
final public const LANGUAGES = core::PUBLIC . DIRECTORY_SEPARATOR . 'languages';
/**
* Default language
*/
final public const LANGUAGE = 'english';
/**
* Read
*
* @param string $id Identifier
* @param string $language Language (name of thw file without ".json")
* @param array &$errors Buffer of errors
*
* @return string|null Text, if found
*/
public static function read(string $id, string $language = 'english', &$errors = []): ?string
{
try {
// Initializing of path to the file of the log
$path = static::LANGUAGES . DIRECTORY_SEPARATOR . "$language.json";
if (file_exists($path)) {
// The file exists
// Open the file of translation
$json = file_get_contents($path);
if (!empty($json)) {
// The file is not empty
// Decoding JSON to Array
$text = json_decode($json, true, 8);
// Exit (success)
return $text[$id] ?? throw new exception('Could not find the text in translation file');
}
}
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
/**
* Generate a list of available languages
*
* @param array &$errors Buffer of errors
*
* @return array|null Languages, if they found
*/
public static function list(&$errors = []): ?array
{
try {
// Initializing of the buffer of languages
$languages = [];
foreach (new parser(static::LANGUAGES) as $file) {
// Iterate through all files in the languages directory
// Skipping system shortcuts
if ($file->isDot()) continue;
if ($file->isReadable() && $file->getSize() > 0) {
// The file is readable and not empty
// Write a language to the buffer registry of available languages
$languages[] = $file->getBasename('.json');
}
}
// Exit (success)
return $languages;
} catch (exception $e) {
// Write to buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -1,86 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models\traits;
// Files of the project
use mirzaev\notchat\models\log,
mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception,
generator;
/**
* Trait of the file handler
*
* @package mirzaev\notchat\models\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait file
{
/**
* Read
*
* @param resource $file File pointer (fopen())
* @param int $limit Maximum limit of iterations (rows)
* @param int $position Initial cursor position
* @param int $step Row reading step
* @param array &$errors Buffer of errors
*
* @return generator|string|null
*/
private static function read($file, int $limit = 500, int $position = 0, int $step = 1, &$errors = []): ?generator
{
try {
while ($limit-- > 0) {
// Recursive execution until $limit reaches 0
// Initializing of the buffer of row
$row = '';
// Initializing the character buffer to generate $row
$character = '';
do {
// Iterate over rows
// End (or beginning) of file reached (success)
if (feof($file)) return;
// Reading a row
$row = $character . $row;
// Move to next position
fseek($file, $position += $step, SEEK_END);
// Read a character
$character = fgetc($file);
// Is the character a carriage return? (end of row)
} while ($character != PHP_EOL);
// Exit (success)
yield $row;
}
// Exit (success)
return null;
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
log::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -1,93 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\notchat\models\traits;
// Files of the project
use mirzaev\notchat\models\log as model,
mirzaev\notchat\models\enumerations\log as type;
// Built-in libraries
use exception;
/**
* Trait of the log handler
*
* @package mirzaev\notchat\models\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait log
{
/**
* Deserializing of type::CONNECTIONS
*
* @param string $row Row from the log type::CONNECTIONS
* @param array &$errors Buffer of errors
*
* @return array [$row, $date, $ip, $forwarded, $referer, $useragent]
*/
private static function connection(string $row, &$errors = []): ?array
{
try {
// Search for parameters of connection
preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)(?:\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]\s?)?(?:\[([^\]]+)\]\s[^$]?)?(?:\[([^\]]+)\]\s?)?$/', trim($row, PHP_EOL), $matches);
// Have all 5 parameters been detected?
if (count($matches) !== 6) throw new exception('Failed to deserialize row');
// Exit (success)
return $matches;
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
/**
* Deserializing of type::BANS
*
* @param string $row Row from the log type::BANS
* @param array &$errors Buffer of errors
*
* @return array [$row, $from, $to, $ip]
*/
private static function ban(string $row, &$errors = []): ?array
{
try {
// Search for parameters of ban
preg_match('/(?:^\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\]\s?)(?:\[([^\]]+)\]\s?)$/', trim($row, PHP_EOL), $matches);
// Have all 3 parameters been detected?
if (count($matches) !== 4) throw new exception('Failed to deserialize row');
// Exit (success)
return $matches;
} catch (exception $e) {
// Write to the buffer of errors
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
// Write to the log of errors
model::write(type::ERRORS, "[{$_SERVER['REMOTE_ADDR']}] " . (empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? '' : "[{$_SERVER['HTTP_X_FORWARDED_FOR']}] ") . $e->getMessage());
}
// Exit (fail)
return null;
}
}

View File

@ -11,11 +11,6 @@
--red: #4d2d2d;
--green: #415e53;
--blue: #243b4f;
--blue-dark: #09262d;
/* --input-servers: #898c25; очень крутой зелёно говняный цвет */
--input-servers-text: #083932;
--input-servers-background: #109386;
}
* {
@ -23,20 +18,20 @@
outline: none;
border: none;
/* font-family: , system-ui, sans-serif; */
/* font-family: "dejavu"; */
font-family: 'DejaVu', sans-serif;
font-family: "dejavu";
transition: 0.1s ease-out;
}
body {
--row-aside: 200px;
--row-settings: 100px;
--gap: 16px;
margin: 0;
width: auto;
height: 100vh;
display: grid;
grid-template-columns: [header] 220px [settings] 320px [main] auto [footer] 180px;
grid-template-rows: [aside] var(--row-aside, 200px) [main] calc(100vh - var(--row-aside));
grid-template-rows: [aside] var(--row-aside, 200px) [settings] var(--row-settings, 100px) [main] auto;
gap: var(--gap, 16px);
padding: 0;
overflow-x: scroll;
@ -63,33 +58,73 @@ header>section[data-section="window"] {
header>section[data-section="main"] {
z-index: 1200;
grid-row: main;
display: flex;
flex-direction: column;
grid-row: settings / -1;
display: grid;
background-color: var(--envelope);
}
aside {
section[data-section="servers"] {
z-index: 250;
grid-row: settings;
background-color: var(--important);
border-radius: 5px;
}
section[data-section="chats"] {
z-index: 200;
margin-bottom: var(--gap);
grid-row: main;
background-color: var(--section);
border-radius: 5px;
}
aside {
z-index: 300;
grid-column: 1 / -1;
grid-row: aside;
display: flex;
}
section[data-section="menu"] {
z-index: 300;
padding: 14px 15px;
main {
--sections-default: 1;
--sections-width: 520px;
z-index: 100;
margin-bottom: var(--gap);
grid-row: settings / -1;
grid-column: main;
display: grid;
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
grid-template-columns: repeat(var(--sections, var(--sections-default, 1)), [chat] var(--sections-width, 520px));
flex-direction: column;
align-items: center;
gap: var(--gap, 16px);
transition: 0s;
}
main>section {
width: 100%;
height: 100%;
max-width: var(--sections-width, 480px);
grid-column: var(--position, 1);
grid-row: main;
grid-column: settings;
overflow-x: crop;
overflow-y: scroll;
background-color: var(--section);
border-radius: 5px;
background-color: var(--important);
}
aside {
z-index: 300;
grid-column: 1 / -1;
grid-row: aside;
display: flex;
}
main {
--sections-default: 1;
--sections-width: 480px;
z-index: 100;
grid-row: main;
grid-row: settings / -1;
grid-column: main;
display: grid;
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
@ -109,6 +144,10 @@ main>section {
background-color: var(--section);
}
main>section[data-panel-type="settings"] {
grid-row: settings;
}
footer {
z-index: 500;
grid-column: footer;
@ -124,16 +163,11 @@ footer>section[data-section="window"] {
footer>section[data-section="main"] {
z-index: 700;
grid-row: main;
display: flex;
flex-direction: column;
grid-row: settings / -1;
display: grid;
background-color: var(--envelope);
}
footer>section[data-section="main"]>#language {
margin-top: auto;
}
:is(div, section).window {
overflow: hidden;
border-right: 1px solid;
@ -150,7 +184,3 @@ footer>section[data-section="main"]>#language {
-ms-user-select: none;
user-select: none;
}
.untouchable {
pointer-events: none;
}

11
mirzaev/notchat/system/public/index.php Executable file → Normal file
View File

@ -21,7 +21,7 @@ define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
define('INDEX', __DIR__);
// Автозагрузка
require INDEX . DIRECTORY_SEPARATOR
require __DIR__ . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
. '..' . DIRECTORY_SEPARATOR
@ -33,14 +33,7 @@ require INDEX . DIRECTORY_SEPARATOR
$router = new router;
// Запись маршрутов
$router->write('/', 'index', 'index', 'GET');
$router->write('/', 'index', 'index', 'POST');
$router->write('/manifest', 'index', 'manifest', 'GET');
$router->write('/offline', 'index', 'offline', 'GET');
$router->write('/cache.js', 'index', 'cache', 'GET');
$router->write('/server/read/$server', 'server', 'read', 'POST');
$router->write('/server/write/$server', 'server', 'write', 'POST');
$router->write('/servers', 'index', 'servers', 'POST');
$router->write('/', 'index', 'index');
// Инициализация ядра
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));

0
mirzaev/notchat/system/public/js/adapter.js Executable file → Normal file
View File

View File

@ -1,83 +0,0 @@
"use strict";
const VERSION = "0.1.0";
const EXPIRED = 86400000;
self.addEventListener("install", (event) => {
});
self.addEventListener("fetch", (event) => {
event
.respondWith(
caches
.match(event.request)
.then((response) => {
if (response) {
// Found file in cache
if (
Date.now() - new Date(response.headers.get("last-modified")) >
EXPIRED
) {
// Expired period of storage response
return fetch(event.request.clone())
.then((response) => {
if (response.status === 200) {
// Downloaded new version
return caches
.open(VERSION)
.then((cache) => {
// Writing new version to cache
cache.put(event.request, response.clone());
// Exit (success)
return response;
});
} else throw "Failed to download new version";
})
.catch(() => {
// Exit (success) (return old version)
return response;
});
} else return response;
} else {
// Not found file in cache
return fetch(event.request.clone())
.then((response) => {
if (response.status === 200) {
// Downloaded
return caches
.open(VERSION)
.then((cache) => {
// Writing to cache
cache.put(event.request, response.clone());
// Exit (success)
return response;
});
} else throw "Failed to download";
});
}
})
.catch(() => {
return caches.match("/offline");
}),
);
});
/* self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(
keyList.map((key) => {
// Deleting old versions of cache
if (VERSION.indexOf(key) === -1) return caches.delete(key);
}),
);
}),
);
}); */

View File

@ -1,252 +0,0 @@
"use strict";
if (typeof window.chats !== "function") {
// Not initialized
// Initialize of the class in global namespace
window.chats = class chats {
/**
* Server
*/
static server = {
/**
* Select a server (dampered)
*
* @param {string} server Domain or IP-address:port of the server (from cache by default)
* @param {bool} force Force execution
*
* @return {void} Into the document will be generated and injected an HTML-element
*/
select(
server = localStorage.server_ip && localStorage.server_port
? localStorage.server_ip + ":" + localStorage.server_port
: localStorage.server_domain,
force = false,
) {
// Writing status: "connecting"
core.menu.setAttribute("data-menu-status", "connecting");
// Deinitializing animation of opening
core.menu.getElementsByTagName("output")[0].classList.remove(
"slide-down",
);
// Initializing animation of closing
core.menu.getElementsByTagName("output")[0].classList.add(
"slide-down-revert",
);
// Disabled for animation
/* core.menu.querySelector('figcaption[data-server="domain"]')
.innerText =
core.menu.querySelector('pre[data-server="description"]')
.innerText =
""; */
this._select(server, force);
},
/**
* Select a server
*
* @param {string} server Domain or IP-address:port of the server (from cache by default)
* @param {bool} force Force execution
*
* @return {void} Into the document will be generated and injected an HTML-element
*/
_select: damper(
(
server = localStorage.server_ip && localStorage.server_port
? localStorage.server_ip + ":" + localStorage.server_port
: localStorage.server_domain,
force = false,
) => {
if (server.length > 512) {
notifications.write(text.read("CHATS_SERVER_ERROR_LENGTH_MAX"));
} else if (typeof server === "string" && server.length > 0) {
if (
core.menu instanceof HTMLElement &&
core.menu.getAttribute("data-menu") === "chats"
) {
//
// Initializing the unlock function
function unblock() {
// Writing status: "empty"
if (
core.menu.querySelector('figcaption[data-server="domain"]')
.innerText.length === 0 &&
core.menu.querySelector('pre[data-server="description"]')
.innerText.length === 0
) {
core.menu.getElementsByTagName("search")[0]
.getElementsByTagName("label")[0].classList.add(
"empty",
);
}
// Writing status: "disconnected"
core.menu.setAttribute("data-menu-status", "disconnected");
}
// Initiating a unlock delay in case the server does not respond
const timeout = setTimeout(() => {
// this.errors(["Server does not respond"]);
unblock();
}, 5000);
core.request(
`/server/read/${encodeURIComponent(server)}`,
`language=${localStorage.language ?? "english"}`,
).then((json) => {
// Deinitializing of unlock delay
clearTimeout(timeout);
if (
json.errors !== null && typeof json.errors === "object" &&
json.errors.length > 0
) {
// Generating notifications with errors
// for (const error of json.errors) notifications.write(error);
// Writing status: "disconnected"
core.menu.setAttribute("data-menu-status", "disconnected");
// Writind domain of the server
core.menu.querySelector('figcaption[data-server="domain"]')
.innerText = "";
// Writing description of the server
core.menu.querySelector('pre[data-server="description"]')
.innerText = "";
// Writing status: "empty" (for opening the description window)
core.menu.getElementsByTagName("search")[0]
.getElementsByTagName("label")[0].classList.add(
"empty",
);
} else {
// Writing status: "connected"
core.menu.setAttribute("data-menu-status", "connected");
// Writind domain of the server
core.menu.querySelector('figcaption[data-server="domain"]')
.innerText = `${json.server.domain}`;
// Writing description of the server
core.menu.querySelector('pre[data-server="description"]')
.innerText = `${json.server.description}`;
// Deleting status: "empty" (for opening the description window) (it is implied that the response from the server cannot be empty)
core.menu.getElementsByTagName("search")[0]
.getElementsByTagName("label")[0].classList.remove(
"empty",
);
// Deinitializing animation of closing
core.menu.getElementsByTagName("output")[0].classList.remove(
"slide-down-revert",
);
// Initializing animation of opening
core.menu.getElementsByTagName("output")[0].classList.add(
"slide-down",
);
// Writing data of the server to local storage (browser)
localStorage.server_domain = json.server.domain;
localStorage.server_ip = json.server.ip;
localStorage.server_port = json.server.port;
}
});
}
}
},
800,
1,
),
};
/**
* Generators
*/
static generate = {
/**
* 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_port}`,
) {
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
) {
// Generating notifications with errors
for (const error of json.errors) notifications.write(error);
} else {
if (core.menu instanceof HTMLElement) {
// Writing status of connection (hack with replaying animations)
core.menu.setAttribute('data-menu-status', 'disconnected');
setTimeout(() => core.menu.setAttribute('data-menu-status', json.status ?? 'disconnected'), 100);
const element = document.createElement("search");
const search = core.menu.getElementsByTagName("search")[0];
if (search instanceof HTMLElement) search.remove();
core.menu.prepend(element);
element.outerHTML = json.html;
core.menu = document.body.querySelector(
"section[data-section='menu']",
);
}
}
});
},
/**
* @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
) {
// Generating notifications with errors
for (const error of json.errors) notifications.write(error);
} else {
// сосать бебру
const element = document.createElement("div");
const position = core.main.children.length;
element.style.setProperty("--position", position);
core.main.append(element);
core.main.style.setProperty("--elements", position + 1);
element.innerHTML = json.html;
}
});
},
};
};
}
// Dispatch event: "initialized"
document.dispatchEvent(
new CustomEvent("chats.initialized", {
detail: { chats: window.chats },
}),
);

View File

@ -1,60 +0,0 @@
"use strict";
if (typeof window.core !== "function") {
// Not initialized
// Initialize of the class in global namespace
window.core = class core {
// Domain
static domain = window.location.hostname;
// Animations are enabled?
static animations = getComputedStyle(document.body).getPropertyValue('--animations') === '1';
// Label for the <header> element
static header = document.body.getElementsByTagName('header')[0];
// Label for the <aside> element
static aside = document.body.getElementsByTagName('aside')[0];
// Label for the "menu" element
static menu = document.body.querySelector("section[data-section='menu']");
// Label for the <main> element
static main = document.body.getElementsByTagName('main')[0];
// Label for the <footer> element
static footer = document.body.getElementsByTagName('footer')[0];
/**
* 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}
*/
static async request(
address = '/',
body,
method = "POST",
headers = {
"Content-Type": "application/x-www-form-urlencoded",
},
type = "json",
) {
return await fetch(address, { method, headers, body })
.then((response) => response[type]());
}
};
}
// Dispatch event: "initialized"
document.dispatchEvent(
new CustomEvent("core.initialized", {
detail: { core: window.core },
}),
);

View File

@ -1,57 +0,0 @@
"use strict";
/**
* Damper
*
* @param {function} function Function to execute after damping
* @param {number} timeout Timer in milliseconds (ms)
* @param {number} force Argument number storing the enforcement status of execution (see @example)
*
* @example
* $a = damper(
* async (
* a, // 0
* b, // 1
* c, // 2
* force = false, // 3
* d // 4
* ) => {
* // Body of function
* },
* 500,
* 3, // 3 -> "force" argument
* );
*
* $a('for a', 'for b', 'for c', true, 'for d'); // Force execute is enabled
*
* @return {void}
*/
function damper(func, timeout = 300, force) {
// Initializing of the timer
let timer;
return (...args) => {
// Deinitializing of the timer
clearTimeout(timer);
if (typeof force === "number" && args[force]) {
// Force execution (ignoring the timer)
func.apply(this, args);
} else {
// Normal execution
// Execute the handled function (entry into recursion)
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
}
};
}
// Dispatch event: "initialized"
document.dispatchEvent(
new CustomEvent("damper.initialized", {
detail: { damper },
}),
);

View File

@ -1,668 +0,0 @@
"use strict";
/**
* Бегущая строка
*
* @description
* Простой, но мощный класс для создания бегущих строк. Поддерживает
* перемещение мышью и прокрутку колесом, полностью настраивается очень гибок
* для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами.
* Имеет свой препроцессор, благодаря которому можно создавать бегущие строки
* без программирования - с помощью HTML-аттрибутов, а так же возможность
* изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать
* события при выбранных действиях для того, чтобы пользователь имел возможность
* дорабатывать функционал без изучения и изменения моего кода
*
* @example
* сonst hotline = new hotline();
* hotline.step = '-5';
* hotline.start();
*
* @todo
* 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты)
*
* @copyright WTFPL
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
class hotline {
// Идентификатор
#id = 0;
// Оболочка (instanceof HTMLElement)
#shell = document.getElementById("hotline");
// Инстанция горячей строки
#instance = null;
// Перемещение
#transfer = true;
// Движение
#move = true;
// Наблюдатель
#observer = null;
// Наблюдатель
#block = new Set(["events"]);
// Настраиваемые параметры
transfer = null;
move = null;
delay = 10;
step = 1;
hover = true;
movable = true;
sticky = false;
wheel = false;
delta = null;
vertical = false;
observe = false;
events = new Map([
["start", false],
["stop", false],
["move", false],
["move.block", false],
["move.unblock", false],
["offset", false],
["transfer.start", true],
["transfer.end", true],
["onmousemove", false]
]);
constructor(id, shell) {
// Запись идентификатора
if (typeof id === "string" || typeof id === "number") this.#id = id;
// Запись оболочки
if (shell instanceof HTMLElement) this.#shell = shell;
}
start() {
if (this.#instance === null) {
// Нет запущенной инстанции бегущей строки
// Инициализация ссылки на ядро
const _this = this;
// Запуск движения
this.#instance = setInterval(function () {
if (_this.#shell.childElementCount > 1) {
// Найдено содержимое бегущей строки (2 и более)
// Инициализация буфера для временных данных
let buffer;
// Инициализация данных первого элемента в строке
const first = {
element: (buffer = _this.#shell.firstElementChild),
coords: buffer.getBoundingClientRect()
};
if (_this.vertical) {
// Вертикальная бегущая строка
// Инициализация сдвига у первого элемента (движение)
first.offset = isNaN(
(buffer = parseFloat(first.element.style.marginTop))
)
? 0
: buffer;
// Инициализация отступа до второго элемента у первого элемента (разделение)
first.separator = isNaN(
(buffer = parseFloat(
getComputedStyle(first.element).marginBottom
))
)
? 0
: buffer;
// Инициализация крайнего с конца ребра первого элемента в строке
first.end = first.coords.y + first.coords.height + first.separator;
} else {
// Горизонтальная бегущая строка
// Инициализация отступа у первого элемента (движение)
first.offset = isNaN(
(buffer = parseFloat(first.element.style.marginLeft))
)
? 0
: buffer;
// Инициализация отступа до второго элемента у первого элемента (разделение)
first.separator = isNaN(
(buffer = parseFloat(getComputedStyle(first.element).marginRight))
)
? 0
: buffer;
// Инициализация крайнего с конца ребра первого элемента в строке
first.end = first.coords.x + first.coords.width + first.separator;
}
if (
(_this.vertical &&
Math.round(first.end) < _this.#shell.offsetTop) ||
(!_this.vertical && Math.round(first.end) < _this.#shell.offsetLeft)
) {
// Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки)
if (
(_this.transfer === null && _this.#transfer) ||
_this.transfer === true
) {
// Перенос разрешен
if (_this.vertical) {
// Вертикальная бегущая строка
// Удаление отступов (движения)
first.element.style.marginTop = null;
} else {
// Горизонтальная бегущая строка
// Удаление отступов (движения)
first.element.style.marginLeft = null;
}
// Копирование первого элемента в конец строки
_this.#shell.appendChild(first.element);
if (_this.events.get("transfer.end")) {
// Запрошен вызов события: "перемещение в конец"
// Вызов события: "перемещение в конец"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.transfer.end`, {
detail: {
element: first.element,
offset: -(
(_this.vertical
? first.coords.height
: first.coords.width) + first.separator
)
}
})
);
}
}
} else if (
(_this.vertical &&
Math.round(first.coords.y) > _this.#shell.offsetTop) ||
(!_this.vertical &&
Math.round(first.coords.x) > _this.#shell.offsetLeft)
) {
// Передняя (движущая) граница первого элемента вышла из области видимости
if (
(_this.transfer === null && _this.#transfer) ||
_this.transfer === true
) {
// Перенос разрешен
// Инициализация отступа у последнего элемента (разделение)
const separator =
(buffer = isNaN(
(buffer = parseFloat(
getComputedStyle(_this.#shell.lastElementChild)[
_this.vertical ? "marginBottom" : "marginRight"
]
))
)
? 0
: buffer) === 0
? first.separator
: buffer;
// Инициализация координат первого элемента в строке
const coords = _this.#shell.lastElementChild.getBoundingClientRect();
if (_this.vertical) {
// Вертикальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginTop =
-coords.height - separator + "px";
} else {
// Горизонтальная бегущая строка
// Удаление отступов (движения)
_this.#shell.lastElementChild.style.marginLeft =
-coords.width - separator + "px";
}
// Копирование последнего элемента в начало строки
_this.#shell.insertBefore(
_this.#shell.lastElementChild,
first.element
);
// Удаление отступов у второго элемента в строке (движения)
_this.#shell.children[1].style[
_this.vertical ? "marginTop" : "marginLeft"
] = null;
if (_this.events.get("transfer.start")) {
// Запрошен вызов события: "перемещение в начало"
// Вызов события: "перемещение в начало"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.transfer.start`, {
detail: {
element: _this.#shell.lastElementChild,
offset:
(_this.vertical ? coords.height : coords.width) +
separator
}
})
);
}
}
} else {
// Элемент в области видимости
if ((_this.move === null && _this.#move) || _this.move === true) {
// Движение разрешено
// Запись новых координат сдвига
const offset = first.offset + _this.step;
// Запись сдвига (движение)
_this.offset(offset);
if (_this.events.get("move")) {
// Запрошен вызов события: "движение"
// Вызов события: "движение"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move`, {
detail: {
from: first.offset,
to: offset
}
})
);
}
}
}
}
}, _this.delay);
if (this.hover) {
// Запрошена возможность останавливать бегущую строку
// Инициализация сдвига
let offset = 0;
// Инициализация слушателя события при перемещении элемента в бегущей строке
const listener = function (e) {
// Увеличение сдвига
offset += e.detail.offset ?? 0;
};
// Инициализация обработчика наведения курсора (остановка движения)
this.#shell.onmouseover = function (e) {
// Курсор наведён на бегущую строку
// Блокировка движения
_this.#move = false;
if (_this.events.get("move.block")) {
// Запрошен вызов события: "блокировка движения"
// Вызов события: "блокировка движения"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move.block`)
);
}
if (_this.movable) {
// Запрошена возможность двигать бегущую строку
_this.#shell.onmousedown = function (onmousedown) {
// Курсор активирован
// Инициализация слушателей события перемещения элемента в бегущей строке
document.addEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.addEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Инициализация буфера для временных данных
let buffer;
// Инициализация данных первого элемента в строке
const first = {
offset: isNaN(
(buffer = parseFloat(
_this.vertical
? _this.#shell.firstElementChild.style.marginTop
: _this.#shell.firstElementChild.style.marginLeft
))
)
? 0
: buffer
};
document.onmousemove = function (onmousemove) {
// Курсор движется
if (_this.vertical) {
// Вертикальная бегущая строка
// Инициализация буфера местоположения
const from = _this.#shell.firstElementChild.style.marginTop;
const to = onmousemove.pageY - (onmousedown.pageY + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginTop = to +
"px";
if (_this.events.get("onmousemove")) {
// Запрошен вызов события: "перемещение мышью"
// Вызов события: "перемещение мышью"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
detail: { from, to }
})
);
}
} else {
// Горизонтальная бегущая строка
// Инициализация буфера местоположения
const from = _this.#shell.firstElementChild.style.marginLeft;
const to = onmousemove.pageX - (onmousedown.pageX + offset - first.offset);
// Движение
_this.#shell.firstElementChild.style.marginLeft = to + "px";
if (_this.events.get("onmousemove")) {
// Запрошен вызов события: "перемещение мышью"
// Вызов события: "перемещение мышью"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.onmousemove`, {
detail: { from, to }
})
);
}
}
// Запись курсора
_this.#shell.style.cursor = "grabbing";
};
};
// Перещапись событий браузера (чтобы не дёргалось)
_this.#shell.ondragstart = null;
_this.#shell.onmouseup = function () {
// Курсор деактивирован
// Остановка обработки движения
document.onmousemove = null;
// Сброс сдвига
offset = 0;
document.removeEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.removeEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Восстановление курсора
_this.#shell.style.cursor = null;
};
}
};
// Инициализация обработчика отведения курсора (остановка движения)
this.#shell.onmouseleave = function (onmouseleave) {
// Курсор отведён от бегущей строки
if (!_this.sticky) {
// Отключено прилипание
// Остановка обработки движения
document.onmousemove = null;
document.removeEventListener(
`hotline.${_this.#id}.transfer.start`,
listener
);
document.removeEventListener(
`hotline.${_this.#id}.transfer.end`,
listener
);
// Восстановление курсора
_this.#shell.style.cursor = null;
}
// Сброс сдвига
offset = 0;
// Разблокировка движения
_this.#move = true;
if (_this.events.get("move.unblock")) {
// Запрошен вызов события: "разблокировка движения"
// Вызов события: "разблокировка движения"
document.dispatchEvent(
new CustomEvent(`hotline.${_this.#id}.move.unblock`)
);
}
};
}
if (this.wheel) {
// Запрошена возможность прокручивать колесом мыши
// Инициализация обработчика наведения курсора (остановка движения)
this.#shell.onwheel = function (e) {
// Курсор наведён на бегущую
// Инициализация буфера для временных данных
let buffer;
// Перемещение
_this.offset(
(isNaN(
(buffer = parseFloat(
_this.#shell.firstElementChild.style[
_this.vertical ? "marginTop" : "marginLeft"
]
))
)
? 0
: buffer) +
(_this.delta === null
? e.wheelDelta
: e.wheelDelta > 0
? _this.delta
: -_this.delta)
);
};
}
}
if (this.observe) {
// Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки
if (this.#observer === null) {
// Отсутствует наблюдатель
// Инициализация ссылки на ядро
const _this = this;
// Инициализация наблюдателя
this.#observer = new MutationObserver(function (mutations) {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
// Запись параметра в инстанцию бегущей строки
_this.write(mutation.attributeName);
}
}
// Перезапуск бегущей строки
_this.restart();
});
// Активация наблюдения
this.#observer.observe(this.#shell, {
attributes: true
});
}
} else if (this.#observer instanceof MutationObserver) {
// Запрошено отключение наблюдения
// Деактивация наблюдения
this.#observer.disconnect();
// Удаление наблюдателя
this.#observer = null;
}
if (this.events.get("start")) {
// Запрошен вызов события: "запуск"
// Вызов события: "запуск"
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.start`));
}
}
stop() {
// Остановка бегущей строки
clearInterval(this.#instance);
// Удаление инстанции интервала
this.#instance = null;
if (this.events.get("stop")) {
// Запрошен вызов события: "остановка"
// Вызов события: "остановка"
document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`));
}
}
restart() {
// Остановка бегущей строки
this.stop();
// Запуск бегущей строки
this.start();
}
write(attribute) {
// Инициализация названия параметра
const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1];
if (typeof parameter === "string") {
// Параметр найден
// Проверка на разрешение изменения
if (this.#block.has(parameter)) return;
// Инициализация значения параметра
const value = this.#shell.getAttribute(attribute);
// Инициализация буфера для временных данных
let buffer;
// Запись параметра
this[parameter] = isNaN((buffer = parseFloat(value)))
? value === "true"
? true
: value === "false"
? false
: value
: buffer;
}
}
offset(value) {
// Запись отступа
this.#shell.firstElementChild.style[
this.vertical ? "marginTop" : "marginLeft"
] = value + "px";
if (this.events.get("offset")) {
// Запрошен вызов события: "сдвиг"
// Вызов события: "сдвиг"
document.dispatchEvent(
new CustomEvent(`hotline.${this.#id}.offset`, {
detail: {
to: value
}
})
);
}
}
static preprocessing(event = false) {
// Инициализация счётчиков инстанций горячей строки
const success = new Set();
let error = 0;
for (const element of document.querySelectorAll('*[data-hotline="true"]')) {
// Перебор бегущих строк
if (typeof element.id === "string") {
// Найден идентификатор
// Инициализация инстанции бегущей строки
const hotline = new this(element.id, element);
for (const attribute of element.getAttributeNames()) {
// Перебор аттрибутов
// Запись параметра в инстанцию бегущей строки
hotline.write(attribute);
}
// Запуск бегущей строки
hotline.start();
// Запись инстанции бегущей строки в элемент
element.hotline = hotline;
// Запись в счётчик успешных инициализаций
success.add(hotline);
} else ++error;
}
if (event) {
// Запрошен вызов события: "предварительная подготовка"
// Вызов события: "предварительная подготовка"
document.dispatchEvent(
new CustomEvent(`hotline.preprocessed`, {
detail: {
success,
error
}
})
);
}
}
}
document.dispatchEvent(
new CustomEvent("hotline.loaded", {
detail: { hotline }
})
);

View File

@ -1,19 +0,0 @@
"use strict";
if (typeof window.journal !== "function") {
// Not initialized
// Initialize of the class in global namespace
window.journal = class cache {
static write(text) {
console.log(`[${core.domain ?? "notchat"}] ${text}`);
}
};
}
// Dispatch event: "initialized"
document.dispatchEvent(
new CustomEvent("journal.initialized", {
detail: { journal: window.journal },
}),
);

0
mirzaev/notchat/system/public/js/notchat.js Executable file → Normal file
View File

View File

@ -1,3 +0,0 @@
"use strict";

7
mirzaev/notchat/system/public/js/pages/chat.js Executable file → Normal file
View File

@ -37,10 +37,3 @@ if (typeof window.asdasd === "function") (() => init_asdasd(asdasd))();
else {document.addEventListener("asdasd.initialized", (e) =>
init_asdasd(e.asdasd));}
function init(chats) {
chats.generate.servers();
}
if (typeof window.chats === "function") (() => init(chats))();
else {document.addEventListener("chats.initialized", (e) =>
init(e.chats));}

View File

@ -1,14 +0,0 @@
try {
navigator.serviceWorker
.register("cache.js")
.then((cache) => {
// Registered
journal.write("ServiceWorker registered: " + cache.scope);
})
.catch((error) => {
// Not registered
journal.write("ServiceWorker not registered: " + error);
});
} catch (error) {
console.error(`pizda ${error}`);
}

View File

@ -1,32 +0,0 @@
"use strict";
if (typeof window.text !== "function") {
// Not initialized
// Initialize of the class in global namespace
window.text = class text {
/**
* Language
*/
static language = {
/**
* Select a language
*
* @param {string} language Name of language
*
* @return {void}
*/
select(language = 'english') {
// Write to the local storage in browser
localStorage.language = language;
},
};
};
}
// Dispatch event: "initialized"
document.dispatchEvent(
new CustomEvent("text.initialized", {
detail: { text: window.text },
}),
);

View File

@ -1,3 +0,0 @@
{
"CHATS_SERVER_ERROR_LENGTH_MAX": "Server address longer than 512 characters"
}

View File

@ -1,3 +0,0 @@
{
"CHATS_SERVER_ERROR_LENGTH_MAX": "Адрес сервера длиннее 512-ти символов"
}

View File

@ -1,87 +0,0 @@
CACHE MANIFEST
CACHE:
/themes/default/css/main.css
/themes/default/css/fonts.css
/themes/default/css/fonts/dejavu.css
/themes/default/css/fonts/fira.css
/themes/default/css/fonts/hack.css
/themes/default/css/animations.css
/themes/default/css/chats.css
/themes/default/images/favicon.ico
/themes/default/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf
/themes/default/fonts/dejavu/DejaVuLGCSans.ttf
/themes/default/fonts/dejavu/DejaVuLGCSans-Oblique.ttf
/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf
/themes/default/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf
/themes/default/fonts/fira/FiraSans-Hair.woff2
/themes/default/fonts/fira/FiraSans-HairItalic.woff2
/themes/default/fonts/fira/FiraSans-UltraLight.woff2
/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff2
/themes/default/fonts/fira/FiraSans-Light.woff2
/themes/default/fonts/fira/FiraSans-LightItalic.woff2
/themes/default/fonts/fira/FiraSans-Regular.woff2
/themes/default/fonts/fira/FiraSans-Italic.woff2
/themes/default/fonts/fira/FiraMono-Medium.woff2
/themes/default/fonts/fira/FiraSans-MediumItalic.woff2
/themes/default/fonts/fira/FiraSans-SemiBold.woff2
/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff2
/themes/default/fonts/fira/FiraSans-Bold.woff2
/themes/default/fonts/fira/FiraSans-BoldItalic.woff2
/themes/default/fonts/fira/FiraSans-ExtraBold.woff2
/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff2
/themes/default/fonts/fira/FiraSans-Heavy.woff2
/themes/default/fonts/fira/FiraSans-HeavyItalic.woff2
/themes/default/fonts/fira/FiraMono-Regular.woff2
/themes/default/fonts/fira/FiraMono-Bold.woff2
/themes/default/fonts/fira/FiraSans-Hair.woff
/themes/default/fonts/fira/FiraSans-HairItalic.woff
/themes/default/fonts/fira/FiraSans-UltraLight.woff
/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff
/themes/default/fonts/fira/FiraSans-Light.woff
/themes/default/fonts/fira/FiraSans-LightItalic.woff
/themes/default/fonts/fira/FiraSans-Regular.woff
/themes/default/fonts/fira/FiraSans-Italic.woff
/themes/default/fonts/fira/FiraMono-Medium.woff
/themes/default/fonts/fira/FiraSans-MediumItalic.woff
/themes/default/fonts/fira/FiraSans-SemiBold.woff
/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff
/themes/default/fonts/fira/FiraSans-Bold.woff
/themes/default/fonts/fira/FiraSans-BoldItalic.woff
/themes/default/fonts/fira/FiraSans-ExtraBold.woff
/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff
/themes/default/fonts/fira/FiraSans-Heavy.woff
/themes/default/fonts/fira/FiraSans-HeavyItalic.woff
/themes/default/fonts/fira/FiraMono-Regular.woff
/themes/default/fonts/fira/FiraMono-Bold.woff
/themes/default/fonts/hack/hack-regular.woff2?sha=3114f1256
/themes/default/fonts/hack/hack-bold.woff2?sha=3114f1256
/themes/default/fonts/hack/hack-italic.woff2?sha=3114f1256
/themes/default/fonts/hack/hack-bolditalic.woff2?sha=3114f1256
/themes/default/fonts/hack/hack-regular.woff?sha=3114f1256
/themes/default/fonts/hack/hack-bold.woff?sha=3114f1256
/themes/default/fonts/hack/hack-italic.woff?sha=3114f1256
/themes/default/fonts/hack/hack-bolditalic.woff?sha=3114f1256
/themes/default/icons/data.css
/themes/default/images/ban.jpg
/themes/default/images/offline.jpg
/js/asdasd/libraries/noble-hashes.js
/js/asdasd/asdasd.js
/js/pages/chat.js
/js/adaoter.js
/js/cache.js
/js/chats.js
/js/core.js
/js/damper.js
/js/notchat.js
/js/notifications.js
/js/text.js
/languages/english.json
/languages/russian.json
/
NETWORK:
*
FALLBACK:
/ /offline

View File

@ -0,0 +1,43 @@
<?php
namespace mirzaev\notchat;
use OpenSwoole\Server,
OpenSwoole\WebSocket\Server as websocket,
OpenSwoole\Http\Request,
OpenSwoole\WebSocket\Frame,
OpenSwoole\Constant;
$server = new websocket("0.0.0.0", 2024, Server::POOL_MODE, Constant::SOCK_TCP | Constant::SSL);
$server->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();

View File

@ -1,76 +0,0 @@
@charset "UTF-8";
:root {
--animations: 1;
}
@keyframes uprise {
0% {
opacity: 0;
filter: blur(2px);
}
100% {
opacity: 1;
filter: blur(0px);
}
}
.animation.uprise {
animation-duration: var(--animation-duration, 0.1s);
animation-name: uprise;
animation-fill-mode: forwards;
animation-timing-function: ease-in;
}
@keyframes slide-down {
0% {
transform: translate(0, -100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
}
100% {
transform: translate(0, 0%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
}
.animation.slide-down {
animation-duration: var(--animation-duration, 0.2s);
animation-name: slide-down;
animation-fill-mode: forwards;
animation-timing-function: cubic-bezier(0, 1, 1, 1);
}
@keyframes slide-down-revert {
0% {
transform: translate(0, 0%);
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
}
100% {
transform: translate(0, -100%);
clip-path: polygon(0% 100%, 100% 100%, 100% 200%, 0% 200%);
}
}
.animation.slide-down-revert {
animation-duration: var(--animation-duration, 0.2s);
animation-name: slide-down-revert;
animation-fill-mode: forwards;
animation-timing-function: cubic-bezier(1, 0, 1, 1);
}
@keyframes marquee {
0% {
left: 0;
}
100% {
left: -100%;
}
}
.animation.marquee {
animation: marquee var(--speed, 3s) linear infinite;
}

View File

@ -1,88 +0,0 @@
@charset "UTF-8";
section[data-section="menu"][data-menu="chats"] {
position: relative;
padding: unset;
background-color: transparent;
}
section[data-section="menu"][data-menu="chats"]>search {
display: flex;
flex-direction: column;
border-radius: 5px;
}
section[data-section="menu"][data-menu="chats"]>search>label {
z-index: 200;
position: relative;
height: 1.9rem;
display: flex;
align-items: center;
overflow: clip;
color: var(--input-servers-text);
}
section[data-section="menu"][data-menu="chats"]>search>label>input {
border-radius: 5px;
transition: 0.3s cubic-bezier(1, 0, 1, 1);
}
section[data-section="menu"][data-menu="chats"]>search>label>input:placeholder-shown {
/* blyaaaa */
background-color: red;
}
section[data-section="menu"][data-menu="chats"]:not([data-menu-status="connecting"])>search>label:has(+ output>figure>figcaption:not(:empty) + pre:not(:empty))>input {
border-radius: 5px 5px 0 0;
transition: 0.3s cubic-bezier(0, 1, 1, 1);
}
section[data-section="menu"][data-menu="chats"]>search>label>i:first-child:first-of-type {
position: absolute;
}
section[data-section="menu"][data-menu="chats"]>search>label>i:first-child:first-of-type+input:first-of-type {
width: 100%;
height: 100%;
padding: 0 10px;
font-weight: 500;
color: var(--input-servers-text);
background-color: var(--input-servers-background);
}
section[data-section="menu"][data-menu="chats"]>search>output {
--max-height: 120px;
z-index: 100;
max-height: var(--max-height, 120px);
overflow: hidden;
border-radius: 0 0 5px 5px;
background-color: var(--important);
transition: .1s ease-in;
}
section[data-section="menu"][data-menu="chats"]>search>output>figure {
margin: 13px 17px;
}
section[data-section="menu"][data-menu="chats"]>search>label+output>figure:has(> figcaption:empty + pre:empty) {
margin: 0 17px;
}
section[data-section="menu"][data-menu="chats"]>search>output>figure>pre[data-server="description"] {
margin: unset;
}
/* section[data-section="menu"][data-menu="chats"]>div#chats:empty {
display: none;
}
*/
section[data-section="menu"][data-menu="chats"]>search>img[data-server="image"] {
z-index: 50;
position: absolute;
max-width: 200px;
max-height: 200px;
}
/* section[data-section="menu"][data-menu="chats"]:not(:is([data-menu-status="connected"], [data-menu-status="disconnected"]))>search>img[data-server="image"] {
opacity: 0;
} */

View File

@ -1,9 +0,0 @@
@import url('/themes/default/css/fonts/fira.css');
@import url('/themes/default/css/fonts/hack.css');
@import url('/themes/default/css/fonts/dejavu.css');
@font-face {
font-family: 'Commissioner';
src: url('/themes/default/fonts/commissioner.ttf');
font-weight: 400;
}

View File

@ -1,34 +0,0 @@
@font-face {
font-family: 'DejaVu';
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-ExtraLight.ttf");
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'DejaVu';
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans.ttf");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'DejaVu';
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-Oblique.ttf");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'DejaVu';
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'DejaVu';
src: url("/themes/default/fonts/dejavu/DejaVuLGCSans-BoldOblique.ttf");
font-weight: 500;
font-style: italic;
}

View File

@ -1,139 +0,0 @@
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Hair.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Hair.woff') format('woff');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-HairItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-HairItalic.woff') format('woff');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-UltraLight.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-UltraLight.woff') format('woff');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-UltraLightItalic.woff') format('woff');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Light.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-LightItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-LightItalic.woff') format('woff');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Regular.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Italic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Italic.woff') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraMono-Medium.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraMono-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-MediumItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-SemiBold.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Bold.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-BoldItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-BoldItalic.woff') format('woff');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-ExtraBold.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-ExtraBold.woff') format('woff');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-ExtraBoldItalic.woff') format('woff');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-Heavy.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-Heavy.woff') format('woff');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: 'Fira';
src: url('/themes/default/fonts/fira/FiraSans-HeavyItalic.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraSans-HeavyItalic.woff') format('woff');
font-weight: 900;
font-style: italic;
}
@font-face {
font-family: 'Fira' Mono;
src: url('/themes/default/fonts/fira/FiraMono-Regular.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraMono-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Fira' Mono;
src: url('/themes/default/fonts/fira/FiraMono-Bold.woff2') format('woff2'), url('/themes/default/fonts/fira/FiraMono-Bold.woff') format('woff');
font-weight: 600;
font-style: normal;
}

View File

@ -1,31 +0,0 @@
@font-face {
font-family: 'Hack';
src: url('/themes/default/fonts/hack/hack-regular.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-regular.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.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-bold.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.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-italic.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.woff2?sha=3114f1256') format('woff2'), url('/themes/default/fonts/hack/hack-bolditalic.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
}

View File

@ -1,38 +0,0 @@
section.hotline {
display: inline-flex;
height: calc(100% - 20px);
padding: 10px 0px;
transition: unset;
/* gap нельзя */
}
section.hotline * {
transition: unset;
}
section.hotline:last-child {
margin-bottom: unset;
}
section.hotline>article {
position: relative;
margin-right: 18px;
width: calc(140px - var(--padding, 0px) * 2);
height: calc(180px - var(--padding, 0px) * 2);
padding: var(--padding, 0px);
display: flex;
align-self: flex-end;
overflow: clip;
border-radius: 3px;
background-color: var(--blue-dark);
/* box-shadow: 0px -6px 6px rgba(0, 0, 0, 0.3); */
}
section.hotline>article:last-child {
margin-right: unset;
}
section.hotline>article>* {
margin: auto;
}

View File

@ -1,51 +0,0 @@
i.icon.data {
--width: 14px;
--height: 14px;
position: relative;
width: var(--width);
height: var(--height);
}
i.icon.data,
i.icon.data::after,
i.icon.data::before {
display: block;
box-sizing: border-box;
border: 2px solid;
border-radius: 50%;
}
i.icon.data::before {
left: 2px;
top: 2px;
width: 6px;
height: 6px;
}
i.icon.data::after {
left: -6px;
top: -6px;
width: 22px;
height: 22px;
background: linear-gradient(to left, currentColor 8px, transparent 0) no-repeat bottom center/2px 8px;
}
i.icon.data::after,
i.icon.data::before {
content: "";
position: absolute;
}
i.icon.data,
i.icon.data::after {
border-top-color: transparent;
border-bottom-color: transparent;
}
label>i.icon.data:first-of-type {
left: 13px;
}
label>i.icon.data+input {
padding-left: 38px !important;
}

View File

@ -1,49 +0,0 @@
section.hotline > article.trash {
--padding: 12px;
flex-direction: column;
align-items: center;
gap: 7px;
cursor: zoom-in;
}
section.hotline > article.trash > h1 {
z-index: 10;
margin-top: 10px;
flex-grow: 4;
font-size: 0.9rem;
}
section.hotline > article.trash :is(p, b) {
z-index: 10;
flex-grow: 1;
font-size: 0.6rem;
text-align: justify;
}
section.hotline > article.trash small {
z-index: 10;
flex-grow: 1;
font-size: 0.5rem;
}
section.hotline > article.trash > img {
z-index: 5;
position: absolute;
left: -5%;
top: -5%;
width: 110%;
height: 110%;
object-position: center;
object-fit: cover;
filter: blur(2px) saturate(30%) brightness(40%);
transition: 0.2s ease-in;
}
section.hotline > article.trash:is(:hover, :active) > img {
left: -20%;
top: -20%;
width: 140%;
height: 140%;
filter: blur(3px) saturate(0) brightness(60%) contrast(150%);
transition: 0.1s ease-out;
}

Some files were not shown because too many files have changed in this diff Show More