generated from mirzaev/pot
Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
28d550d53c | ||
|
8627411c8e | ||
|
47687e75b1 | ||
|
2f1c412795 |
2
asdasd
2
asdasd
|
@ -1 +1 @@
|
|||
Subproject commit c18318f5de0e34f6af1491f8e550ffd273c55450
|
||||
Subproject commit 07b2c6ecfc929858d5905fa2159f5cba9c6228a1
|
|
@ -24,7 +24,7 @@
|
|||
},
|
||||
"require": {
|
||||
"php": "~8.3",
|
||||
"ext-openswoole": "~20230831",
|
||||
"ext-sodium": "~8.3",
|
||||
"mirzaev/minimal": "^2.2.0",
|
||||
"twig/twig": "^3.4"
|
||||
},
|
||||
|
|
|
@ -6,7 +6,10 @@ namespace mirzaev\notchat\controllers;
|
|||
|
||||
// Files of the project
|
||||
use mirzaev\notchat\views\templater,
|
||||
mirzaev\notchat\models\core as models;
|
||||
mirzaev\notchat\models\core as models,
|
||||
mirzaev\notchat\models\log,
|
||||
mirzaev\notchat\models\enumerations\log as type,
|
||||
mirzaev\notchat\models\firewall;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\controller;
|
||||
|
@ -30,7 +33,7 @@ class core extends controller
|
|||
protected array $errors = [];
|
||||
|
||||
/**
|
||||
* Constructor of an instance
|
||||
* Constructor
|
||||
*
|
||||
* @param bool $initialize Initialize a controller?
|
||||
*
|
||||
|
@ -44,12 +47,49 @@ class core extends controller
|
|||
if ($initialize) {
|
||||
// Initializing is requested
|
||||
|
||||
// Initializing of models core
|
||||
new models();
|
||||
// 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: /');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
// Analyze recent requests
|
||||
firewall::analyze();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,5 +108,4 @@ class core extends controller
|
|||
default => isset($this->{$name})
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ declare(strict_types=1);
|
|||
namespace mirzaev\notchat\controllers;
|
||||
|
||||
// Files of the project
|
||||
use mirzaev\notchat\controllers\core;
|
||||
use mirzaev\notchat\controllers\core,
|
||||
mirzaev\notchat\models\dns,
|
||||
mirzaev\notchat\models\server,
|
||||
mirzaev\notchat\models\text;
|
||||
|
||||
/**
|
||||
* Index controller
|
||||
|
@ -22,6 +25,62 @@ 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');
|
||||
|
@ -29,4 +88,151 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,9 @@ 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;
|
||||
|
||||
|
@ -23,6 +26,16 @@ 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
|
||||
*
|
||||
|
@ -108,4 +121,3 @@ class core extends model
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,252 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?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';
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?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()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
|
|||
define('INDEX', __DIR__);
|
||||
|
||||
// Автозагрузка
|
||||
require __DIR__ . DIRECTORY_SEPARATOR
|
||||
require INDEX . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
. '..' . DIRECTORY_SEPARATOR
|
||||
|
@ -33,7 +33,14 @@ require __DIR__ . DIRECTORY_SEPARATOR
|
|||
$router = new router;
|
||||
|
||||
// Запись маршрутов
|
||||
$router->write('/', 'index', 'index');
|
||||
$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');
|
||||
|
||||
// Инициализация ядра
|
||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
"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);
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}); */
|
|
@ -0,0 +1,252 @@
|
|||
"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 },
|
||||
}),
|
||||
);
|
|
@ -0,0 +1,60 @@
|
|||
"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 },
|
||||
}),
|
||||
);
|
|
@ -0,0 +1,57 @@
|
|||
"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 },
|
||||
}),
|
||||
);
|
|
@ -0,0 +1,668 @@
|
|||
"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 }
|
||||
})
|
||||
);
|
|
@ -0,0 +1,19 @@
|
|||
"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,0 +1,3 @@
|
|||
"use strict";
|
||||
|
||||
|
|
@ -37,3 +37,10 @@ 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));}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
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}`);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
"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 },
|
||||
}),
|
||||
);
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"CHATS_SERVER_ERROR_LENGTH_MAX": "Server address longer than 512 characters"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"CHATS_SERVER_ERROR_LENGTH_MAX": "Адрес сервера длиннее 512-ти символов"
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
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
|
|
@ -1,43 +0,0 @@
|
|||
<?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();
|
|
@ -0,0 +1,76 @@
|
|||
@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;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
@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;
|
||||
} */
|
|
@ -0,0 +1,9 @@
|
|||
@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;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
@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;
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
@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;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
@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;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
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;
|
||||
}
|
100
mirzaev/notchat/system/public/css/themes/default/main.css → mirzaev/notchat/system/public/themes/default/css/main.css
Normal file → Executable file
100
mirzaev/notchat/system/public/css/themes/default/main.css → mirzaev/notchat/system/public/themes/default/css/main.css
Normal file → Executable file
|
@ -11,6 +11,11 @@
|
|||
--red: #4d2d2d;
|
||||
--green: #415e53;
|
||||
--blue: #243b4f;
|
||||
--blue-dark: #09262d;
|
||||
|
||||
/* --input-servers: #898c25; очень крутой зелёно говняный цвет */
|
||||
--input-servers-text: #083932;
|
||||
--input-servers-background: #109386;
|
||||
}
|
||||
|
||||
* {
|
||||
|
@ -18,20 +23,20 @@
|
|||
outline: none;
|
||||
border: none;
|
||||
/* font-family: , system-ui, sans-serif; */
|
||||
font-family: "dejavu";
|
||||
/* font-family: "dejavu"; */
|
||||
font-family: 'DejaVu', sans-serif;
|
||||
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) [settings] var(--row-settings, 100px) [main] auto;
|
||||
grid-template-rows: [aside] var(--row-aside, 200px) [main] calc(100vh - var(--row-aside));
|
||||
gap: var(--gap, 16px);
|
||||
padding: 0;
|
||||
overflow-x: scroll;
|
||||
|
@ -58,73 +63,33 @@ header>section[data-section="window"] {
|
|||
|
||||
header>section[data-section="main"] {
|
||||
z-index: 1200;
|
||||
grid-row: settings / -1;
|
||||
display: grid;
|
||||
grid-row: main;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--envelope);
|
||||
}
|
||||
|
||||
section[data-section="servers"] {
|
||||
aside {
|
||||
z-index: 250;
|
||||
grid-row: settings;
|
||||
grid-column: 1 / -1;
|
||||
grid-row: aside;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
section[data-section="menu"] {
|
||||
z-index: 300;
|
||||
padding: 14px 15px;
|
||||
grid-row: main;
|
||||
grid-column: settings;
|
||||
border-radius: 5px;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
overflow-x: crop;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--section);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
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: settings / -1;
|
||||
grid-row: main;
|
||||
grid-column: main;
|
||||
display: grid;
|
||||
grid-template-rows: [settings] var(--row-settings, 100px) [main] auto;
|
||||
|
@ -144,10 +109,6 @@ main>section {
|
|||
background-color: var(--section);
|
||||
}
|
||||
|
||||
main>section[data-panel-type="settings"] {
|
||||
grid-row: settings;
|
||||
}
|
||||
|
||||
footer {
|
||||
z-index: 500;
|
||||
grid-column: footer;
|
||||
|
@ -163,11 +124,16 @@ footer>section[data-section="window"] {
|
|||
|
||||
footer>section[data-section="main"] {
|
||||
z-index: 700;
|
||||
grid-row: settings / -1;
|
||||
display: grid;
|
||||
grid-row: main;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--envelope);
|
||||
}
|
||||
|
||||
footer>section[data-section="main"]>#language {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
:is(div, section).window {
|
||||
overflow: hidden;
|
||||
border-right: 1px solid;
|
||||
|
@ -184,3 +150,7 @@ footer>section[data-section="main"] {
|
|||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.untouchable {
|
||||
pointer-events: none;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
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;
|
||||
}
|
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/dejavu/DejaVuLGCSans-Bold.ttf
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-BoldItalic.woff
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-BoldItalic.woff
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-BookItalic.woff
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-BookItalic.woff
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-ExtraBold.woff2
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-ExtraBold.woff2
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-ExtraLight.woff
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-ExtraLight.woff
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-FourItalic.woff
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-FourItalic.woff
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-HairItalic.woff
Executable file
BIN
mirzaev/notchat/system/public/themes/default/fonts/fira/FiraSans-HairItalic.woff
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue