diff --git a/.gitignore b/.gitignore
index 3d2d60e..163dfc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
/sftp-config.json
/tg.pyr
/config/wg0.conf
+/config/hwid.json
/todo.todo
/app/zapretlists/*
!/app/zapretlists/.gitkeep
diff --git a/app/bot.php b/app/bot.php
index 1af7e52..618d3a7 100644
--- a/app/bot.php
+++ b/app/bot.php
@@ -14,6 +14,7 @@ class Bot
public $logs;
public $reg;
public $pool;
+ public $hwid;
public function __construct($key, $i18n)
{
@@ -31,6 +32,7 @@ class Bot
$this->limit = $this->getPacConf()['limitpage'] ?: 5;
$this->adguard = '/config/AdGuardHome.yaml';
$this->update = '/update/json';
+ $this->hwid = '/config/hwid.json';
$this->logs = [
'nginx_default_access',
'nginx_domain_access',
@@ -168,6 +170,30 @@ class Bot
case preg_match('~^/setIpLimit$~', $this->input['callback'], $m):
$this->setIpLimit();
break;
+ case preg_match('~^/hwidLimit$~', $this->input['callback'], $m):
+ $this->hwidLimit();
+ break;
+ case preg_match('~^/toggleHwidLimit(?: (\w+))?$~', $this->input['callback'], $m):
+ $this->toggleHwidLimit($m[1] ?? null);
+ break;
+ case preg_match('~^/setHwidDevices(?: (\w+))?$~', $this->input['callback'], $m):
+ $this->setHwidDevices($m[1] ?? null);
+ break;
+ case preg_match('~^/hwidUser (\d+)(?:_(\d+))?$~', $this->input['callback'], $m):
+ $this->hwidUser($m[1], $m[2] ?? 0);
+ break;
+ case preg_match('~^/hwidUserToggle (\d+)$~', $this->input['callback'], $m):
+ $this->hwidUserToggle($m[1]);
+ break;
+ case preg_match('~^/hwidUserDefault (\d+)$~', $this->input['callback'], $m):
+ $this->hwidUserDefault($m[1]);
+ break;
+ case preg_match('~^/setHwidUserLimit (\d+)$~', $this->input['callback'], $m):
+ $this->setHwidUserLimit($m[1]);
+ break;
+ case preg_match('~^/hwidUserDel (\d+)_(\d+) (.+)$~', $this->input['callback'], $m):
+ $this->hwidUserDel($m[1], $m[2], $m[3]);
+ break;
case preg_match('~^/searchLogs (.+)$~', $this->input['message'], $m):
$this->searchLogs($m[1]);
break;
@@ -4718,6 +4744,228 @@ DNS-over-HTTPS with IP:
$this->xray();
}
+ public function hwidLimit()
+ {
+ $pac = $this->getPacConf();
+ $enabled = !empty($pac['hwid_limit_enabled']);
+ $count = max(1, (int) ($pac['hwid_device_count'] ?: 1));
+
+ $text[] = 'Settings -> ' . $this->i18n('hwid limit');
+ $text[] = $this->i18n('hwid notice');
+ $text[] = $this->i18n('hwid limit') . ': ' . ($enabled ? $count : $this->i18n('off'));
+
+ $data[] = [
+ [
+ 'text' => $this->i18n($enabled ? 'on' : 'off'),
+ 'callback_data' => '/toggleHwidLimit',
+ ],
+ ];
+ $data[] = [
+ [
+ 'text' => $this->i18n('set hwid devices count') . ': ' . $count,
+ 'callback_data' => '/setHwidDevices',
+ ],
+ ];
+ $data[] = [
+ [
+ 'text' => $this->i18n('back'),
+ 'callback_data' => '/xray',
+ ],
+ ];
+
+ $this->update(
+ $this->input['chat'],
+ $this->input['message_id'],
+ implode("\n", $text ?: ['...']),
+ $data ?: false,
+ );
+ }
+
+ public function toggleHwidLimit($context = null)
+ {
+ $pac = $this->getPacConf();
+ $pac['hwid_limit_enabled'] = $pac['hwid_limit_enabled'] ? 0 : 1;
+ if (!empty($pac['hwid_limit_enabled']) && empty($pac['hwid_device_count'])) {
+ $pac['hwid_device_count'] = 1;
+ }
+ $this->setPacConf($pac);
+ $this->answer($this->input['callback_id'], $this->i18n('hwid notice'), true);
+ if ($context === 'xray') {
+ $this->xray();
+ } else {
+ $this->hwidLimit();
+ }
+ }
+
+ public function setHwidDevices($context = null)
+ {
+ $r = $this->send(
+ $this->input['chat'],
+ "@{$this->input['username']} enter hwid devices count",
+ $this->input['message_id'],
+ reply: 'enter hwid devices count',
+ );
+ $_SESSION['reply'][$r['result']['message_id']] = [
+ 'start_message' => $this->input['message_id'],
+ 'callback' => 'saveHwidDevices',
+ 'args' => [$context],
+ ];
+ }
+
+ public function saveHwidDevices($count, $context = null)
+ {
+ $count = (int) $count;
+ if ($count <= 0) {
+ $count = 1;
+ }
+ $pac = $this->getPacConf();
+ $pac['hwid_device_count'] = $count;
+ $this->setPacConf($pac);
+ $this->send($this->input['chat'], $this->i18n('hwid notice'), $this->input['message_id']);
+ if ($context === 'xray') {
+ $this->xray();
+ } else {
+ $this->hwidLimit();
+ }
+ }
+
+ public function getHwidStorage()
+ {
+ if (!file_exists($this->hwid)) {
+ return [];
+ }
+ $data = json_decode(file_get_contents($this->hwid), true);
+ return is_array($data) ? $data : [];
+ }
+
+ public function setHwidStorage(array $storage)
+ {
+ $dir = dirname($this->hwid);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0777, true);
+ }
+ file_put_contents($this->hwid, json_encode($storage, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+ }
+
+ public function getHwidDevicesByUser($uid)
+ {
+ $storage = $this->getHwidStorage();
+ return $storage[$uid] ?? [];
+ }
+
+ public function setHwidDevice($uid, $hwid, array $info)
+ {
+ $storage = $this->getHwidStorage();
+ $storage[$uid][$hwid] = $info;
+ $this->setHwidStorage($storage);
+ }
+
+ public function deleteHwidDevice($uid, $hwid)
+ {
+ $storage = $this->getHwidStorage();
+ if (isset($storage[$uid][$hwid])) {
+ unset($storage[$uid][$hwid]);
+ if (empty($storage[$uid])) {
+ unset($storage[$uid]);
+ }
+ $this->setHwidStorage($storage);
+ }
+ }
+
+ public function deleteHwidUser($uid)
+ {
+ $storage = $this->getHwidStorage();
+ if (isset($storage[$uid])) {
+ unset($storage[$uid]);
+ $this->setHwidStorage($storage);
+ }
+ }
+
+ protected function getHwidTokenScope($index)
+ {
+ return ($this->input['chat'] ?? 'global') . ':' . $index;
+ }
+
+ protected function rememberHwidToken($scope, $hwid)
+ {
+ if (!isset($_SESSION['hwidTokens'])) {
+ $_SESSION['hwidTokens'] = [];
+ }
+ if (!isset($_SESSION['hwidTokens'][$scope])) {
+ $_SESSION['hwidTokens'][$scope] = [];
+ }
+ do {
+ try {
+ $token = bin2hex(random_bytes(5));
+ } catch (\Throwable $e) {
+ $token = substr(hash('sha256', $hwid . microtime(true)), 0, 10);
+ }
+ } while (isset($_SESSION['hwidTokens'][$scope][$token]));
+
+ $_SESSION['hwidTokens'][$scope][$token] = $hwid;
+
+ return $token;
+ }
+
+ protected function resolveHwidToken($scope, $token)
+ {
+ if (isset($_SESSION['hwidTokens'][$scope][$token])) {
+ $hwid = $_SESSION['hwidTokens'][$scope][$token];
+ unset($_SESSION['hwidTokens'][$scope][$token]);
+ return $hwid;
+ }
+
+ $decoded = base64_decode($token, true);
+
+ return $decoded !== false ? $decoded : '';
+ }
+
+ public function processHwidRequest(array $client)
+ {
+ $pac = $this->getPacConf();
+ if (empty($pac['hwid_limit_enabled']) || !empty($client['hwid_disabled'])) {
+ return true;
+ }
+
+ $limit = (int) ($client['hwid_limit'] ?: ($pac['hwid_device_count'] ?: 0));
+ if ($limit <= 0) {
+ return true;
+ }
+
+ $devices = $this->getHwidDevicesByUser($client['id']);
+ $hwid = trim($_SERVER['HTTP_X_HWID'] ?? '');
+ $over = false;
+
+ if ($hwid === '') {
+ if (count($devices) >= $limit) {
+ $over = true;
+ }
+ } else {
+ $isNew = !isset($devices[$hwid]);
+ if ($isNew && count($devices) >= $limit) {
+ $over = true;
+ } else {
+ $this->setHwidDevice($client['id'], $hwid, [
+ 'time' => time(),
+ 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
+ 'device_os' => $_SERVER['HTTP_X_DEVICE_OS'] ?? '',
+ 'os_version' => $_SERVER['HTTP_X_VER_OS'] ?? '',
+ 'device_model' => $_SERVER['HTTP_X_DEVICE_MODEL'] ?? '',
+ ]);
+ }
+ }
+
+ if ($over) {
+ $message = 'HWID device limit exceeded';
+ header('announce: base64:' . base64_encode($message));
+ header('X-HWID-Status: ' . $message);
+ header('HTTP/1.1 429 Too Many Requests', true, 429);
+ return false;
+ }
+
+ return true;
+ }
+
public function switchSilence()
{
$c = $this->getPacConf();
@@ -5553,6 +5801,7 @@ DNS-over-HTTPS with IP:
$st = $this->getXrayStats();
foreach ($r['inbounds'][0]['settings']['clients'] as $k => $v) {
if ($i == $k) {
+ $this->deleteHwidUser($r['inbounds'][0]['settings']['clients'][$k]['id']);
unset($r['inbounds'][0]['settings']['clients'][$k]);
unset($st['users'][$k]);
$this->setXrayStats($st);
@@ -6024,13 +6273,25 @@ DNS-over-HTTPS with IP:
'callback_data' => "/changeTransport 1",
],
];
- $ip_count = $p['ip_count'] ?: 1;
+ $ip_count = $p['ip_count'] ?: 1;
+ $hwidEnabled = !empty($p['hwid_limit_enabled']);
+ $defaultHwids = max(1, (int) ($p['hwid_device_count'] ?: 1));
$data[] = [
[
'text' => $this->i18n('ip limit') . ' ' . ($p['ip_limit'] ? ": {$p['ip_limit']} sec & $ip_count" : $this->i18n('off')),
'callback_data' => "/setIpLimit",
],
];
+ $data[] = [
+ [
+ 'text' => $this->i18n('hwid limit') . ': ' . $this->i18n($hwidEnabled ? 'on' : 'off') . " ({$defaultHwids})",
+ 'callback_data' => '/toggleHwidLimit xray',
+ ],
+ [
+ 'text' => $this->i18n('set hwid devices count'),
+ 'callback_data' => '/setHwidDevices xray',
+ ],
+ ];
if ($p['transport'] == 'Reality') {
$data[] = [
[
@@ -6416,12 +6677,18 @@ DNS-over-HTTPS with IP:
public function userXr($i)
{
- $c = $this->getXray()['inbounds'][0]['settings']['clients'][$i];
+ $xray = $this->getXray();
+ $c = $xray['inbounds'][0]['settings']['clients'][$i];
$pac = $this->getPacConf();
$domain = $this->getDomain($pac['transport'] != 'Reality');
$scheme = empty($this->nginxGetTypeCert()) ? 'http' : 'https';
$hash = $this->getHashBot();
+ $devices = $this->getHwidDevicesByUser($c['id']);
+ $hwidEnabled = !empty($pac['hwid_limit_enabled']) && empty($c['hwid_disabled']);
+ $defaultHwid = max(1, (int) ($pac['hwid_device_count'] ?: 1));
+ $hwidLimit = $c['hwid_limit'] ? (int) $c['hwid_limit'] : $defaultHwid;
+
$text[] = "Menu -> " . $this->i18n('xray') . " -> {$c['email']}\n";
if (file_exists(__DIR__ . '/subscription.php')) {
$text[] = "subscription";
@@ -6468,15 +6735,15 @@ DNS-over-HTTPS with IP:
$data[] = [
[
'text' => $this->i18n('v2ray'),
- 'web_app' => ['url' => "https://{$domain}/pac$hash?t=s&s={$c['id']}"],
+ 'web_app' => ['url' => "https://{$domain}/pac$hash?t=s&s={$c['id']}"]
],
[
'text' => $this->i18n('singbox'),
- 'web_app' => ['url' => "https://{$domain}/pac$hash?t=si&s={$c['id']}"],
+ 'web_app' => ['url' => "https://{$domain}/pac$hash?t=si&s={$c['id']}"]
],
[
'text' => $this->i18n('mihomo'),
- 'web_app' => ['url' => "https://{$domain}/pac$hash?t=cl&s={$c['id']}"],
+ 'web_app' => ['url' => "https://{$domain}/pac$hash?t=cl&s={$c['id']}"]
],
];
$data[] = [
@@ -6520,6 +6787,12 @@ DNS-over-HTTPS with IP:
'callback_data' => "/qrXray {$i}_2",
],
];
+ $data[] = [
+ [
+ 'text' => $this->i18n('hwid limit') . ': ' . ($hwidEnabled ? $hwidLimit : $this->i18n('off')) . ' (' . count($devices) . ')',
+ 'callback_data' => "/hwidUser $i",
+ ],
+ ];
$data[] = [
[
'text' => $this->i18n('rename'),
@@ -6544,6 +6817,188 @@ DNS-over-HTTPS with IP:
);
}
+ public function hwidUser($i, $page = 0)
+ {
+ $xray = $this->getXray();
+ $client = $xray['inbounds'][0]['settings']['clients'][$i];
+ $pac = $this->getPacConf();
+
+ $devices = $this->getHwidDevicesByUser($client['id']);
+ $scope = $this->getHwidTokenScope($i);
+ if (!isset($_SESSION['hwidTokens'])) {
+ $_SESSION['hwidTokens'] = [];
+ }
+ $_SESSION['hwidTokens'][$scope] = [];
+ uasort($devices, fn($a, $b) => ($b['time'] ?? 0) <=> ($a['time'] ?? 0));
+ $hwids = array_keys($devices);
+ $perPage = max(1, $this->limit ?: 5);
+ $total = count($hwids);
+ $pages = max(1, (int) ceil($total / $perPage));
+ $page = min(max((int) $page, 0), $pages - 1);
+ $hwidsPage = array_slice($hwids, $page * $perPage, $perPage);
+ $defaultHwid = max(1, (int) ($pac['hwid_device_count'] ?: 1));
+
+ $text[] = "Menu -> " . $this->i18n('xray') . " -> {$client['email']} -> " . $this->i18n('hwid devices');
+ $text[] = $this->i18n('hwid notice');
+ if (empty($pac['hwid_limit_enabled'])) {
+ $status = $this->i18n('off');
+ } elseif (!empty($client['hwid_disabled'])) {
+ $status = $this->i18n('off');
+ } elseif (!empty($client['hwid_limit'])) {
+ $status = (int) $client['hwid_limit'];
+ } else {
+ $status = "default($defaultHwid)";
+ }
+ $text[] = $this->i18n('hwid limit') . ': ' . $status;
+ $text[] = $this->i18n('hwid devices') . ': ' . $total;
+
+ $data[] = [
+ [
+ 'text' => $this->i18n(!empty($client['hwid_disabled']) ? 'off' : 'on'),
+ 'callback_data' => "/hwidUserToggle $i",
+ ],
+ ];
+ $data[] = [
+ [
+ 'text' => $this->i18n('set hwid devices count'),
+ 'callback_data' => "/setHwidUserLimit $i",
+ ],
+ ];
+ if (!empty($client['hwid_limit'])) {
+ $data[] = [
+ [
+ 'text' => $this->i18n('use default hwid limit'),
+ 'callback_data' => "/hwidUserDefault $i",
+ ],
+ ];
+ }
+
+ if ($total == 0) {
+ $text[] = $this->i18n('no devices');
+ }
+
+ foreach ($hwidsPage as $index => $hwid) {
+ $info = $devices[$hwid];
+ $number = $page * $perPage + $index + 1;
+ $details = array_filter([
+ $info['device_os'] ?? '',
+ $info['os_version'] ?? '',
+ $info['device_model'] ?? '',
+ ], fn($v) => $v !== '');
+ $line = $number . '. ' . htmlspecialchars($hwid, ENT_HTML5, 'UTF-8') . '';
+ if (!empty($details)) {
+ $line .= ' - ' . htmlspecialchars(implode(' ', $details), ENT_HTML5, 'UTF-8');
+ }
+ if (!empty($info['time'])) {
+ $line .= ' (' . date('d.m.Y H:i', $info['time']) . ')';
+ }
+ $text[] = $line;
+ if (!empty($info['user_agent'])) {
+ $text[] = 'UA: ' . htmlspecialchars($info['user_agent'], ENT_HTML5, 'UTF-8');
+ }
+ $token = $this->rememberHwidToken($scope, $hwid);
+ $data[] = [
+ [
+ 'text' => 'π ' . $number,
+ 'callback_data' => "/hwidUserDel {$i}_{$page} $token",
+ ],
+ ];
+ }
+
+ if ($pages > 1) {
+ $data[] = [
+ [
+ 'text' => '<<',
+ 'callback_data' => "/hwidUser {$i}_" . ($page - 1 >= 0 ? $page - 1 : $pages - 1),
+ ],
+ [
+ 'text' => ($page + 1) . '/' . $pages,
+ 'callback_data' => "/hwidUser {$i}_$page",
+ ],
+ [
+ 'text' => '>>',
+ 'callback_data' => "/hwidUser {$i}_" . (($page + 1) % $pages),
+ ],
+ ];
+ }
+
+ $data[] = [
+ [
+ 'text' => $this->i18n('back'),
+ 'callback_data' => "/userXr $i",
+ ],
+ ];
+
+ $this->update(
+ $this->input['chat'],
+ $this->input['message_id'],
+ implode("\n", $text ?: ['...']),
+ $data ?: false,
+ );
+ }
+
+ public function hwidUserToggle($i)
+ {
+ $xray = $this->getXray();
+ if (!empty($xray['inbounds'][0]['settings']['clients'][$i]['hwid_disabled'])) {
+ unset($xray['inbounds'][0]['settings']['clients'][$i]['hwid_disabled']);
+ } else {
+ $xray['inbounds'][0]['settings']['clients'][$i]['hwid_disabled'] = 1;
+ }
+ file_put_contents('/config/xray.json', json_encode($xray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+ $this->answer($this->input['callback_id'], $this->i18n('hwid notice'), true);
+ $this->hwidUser($i);
+ }
+
+ public function hwidUserDefault($i)
+ {
+ $xray = $this->getXray();
+ unset($xray['inbounds'][0]['settings']['clients'][$i]['hwid_limit']);
+ file_put_contents('/config/xray.json', json_encode($xray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+ $this->hwidUser($i);
+ }
+
+ public function setHwidUserLimit($i)
+ {
+ $r = $this->send(
+ $this->input['chat'],
+ "@{$this->input['username']} enter hwid devices count",
+ $this->input['message_id'],
+ reply: 'enter hwid devices count',
+ );
+ $_SESSION['reply'][$r['result']['message_id']] = [
+ 'start_message' => $this->input['message_id'],
+ 'callback' => 'saveHwidUserLimit',
+ 'args' => [$i],
+ ];
+ }
+
+ public function saveHwidUserLimit($count, $i)
+ {
+ $xray = $this->getXray();
+ $count = (int) $count;
+ if ($count > 0) {
+ $xray['inbounds'][0]['settings']['clients'][$i]['hwid_limit'] = $count;
+ } else {
+ unset($xray['inbounds'][0]['settings']['clients'][$i]['hwid_limit']);
+ }
+ file_put_contents('/config/xray.json', json_encode($xray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+ $this->send($this->input['chat'], $this->i18n('hwid notice'), $this->input['message_id']);
+ $this->hwidUser($i);
+ }
+
+ public function hwidUserDel($i, $page, $hwid)
+ {
+ $xray = $this->getXray();
+ $uid = $xray['inbounds'][0]['settings']['clients'][$i]['id'];
+ $scope = $this->getHwidTokenScope($i);
+ $decoded = $this->resolveHwidToken($scope, $hwid);
+ if ($decoded !== '') {
+ $this->deleteHwidDevice($uid, $decoded);
+ }
+ $this->hwidUser($i, $page);
+ }
+
public function getDomain($cdn = false)
{
$c = $this->getPacConf();
@@ -6562,6 +7017,7 @@ DNS-over-HTTPS with IP:
$scheme = empty($this->nginxGetTypeCert()) ? 'http' : 'https';
$hash = $this->getHashBot();
$flag = true;
+ $client = null;
foreach ($xr['inbounds'][0]['settings']['clients'] as $k => $v) {
if ($v['id'] == $_GET['id']) {
if (empty($v['off'])) {
@@ -6570,9 +7026,13 @@ DNS-over-HTTPS with IP:
$uid = $v['id'];
$email = $v['email'];
$expire = $v['time'];
+ $client = $v;
break;
}
}
+ if (!$flag && !$this->processHwidRequest($client)) {
+ exit;
+ }
$suburl = "subscription";
$download = $this->getBytes($st['users'][$k]['global']['download'] + $st['users'][$k]['session']['download']);
$upload = $this->getBytes($st['users'][$k]['global']['upload'] + $st['users'][$k]['session']['upload']);
@@ -6625,6 +7085,7 @@ DNS-over-HTTPS with IP:
$hash = $this->getHashBot();
$flag = true;
+ $client = null;
foreach ($xr['inbounds'][0]['settings']['clients'] as $k => $v) {
if ($v['id'] == $_GET['s']) {
if (empty($v['off'])) {
@@ -6633,6 +7094,7 @@ DNS-over-HTTPS with IP:
$template = base64_decode($v["{$type}template"]);
$uid = $v['id'];
$email = $v['email'];
+ $client = $v;
break;
}
}
@@ -6641,6 +7103,10 @@ DNS-over-HTTPS with IP:
exit;
}
+ if (!$return && !$this->processHwidRequest($client)) {
+ exit;
+ }
+
if (!empty($_GET['r'])) {
$si = "$scheme://{$domain}/pac$hash/" . base64_encode(serialize([
'h' => $hash,
diff --git a/app/i18n.php b/app/i18n.php
index d541016..756f98c 100644
--- a/app/i18n.php
+++ b/app/i18n.php
@@ -193,6 +193,30 @@ $i = [
'en' => 'delete internal dns',
'ru' => 'ΡΠ΄Π°Π»ΠΈΡΡ Π²Π½ΡΡΡΠ΅Π½Π½ΠΈΠΉ dns',
],
+ 'hwid limit' => [
+ 'en' => 'HWID limit',
+ 'ru' => 'HWID Π»ΠΈΠΌΠΈΡ',
+ ],
+ 'hwid devices' => [
+ 'en' => 'HWID devices',
+ 'ru' => 'Π£ΡΡΡΠΎΠΉΡΡΠ²Π° HWID',
+ ],
+ 'set hwid devices count' => [
+ 'en' => 'set HWID devices count',
+ 'ru' => 'ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ HWID ΡΡΡΡΠΎΠΉΡΡΠ²',
+ ],
+ 'use default hwid limit' => [
+ 'en' => 'use default limit',
+ 'ru' => 'ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ Π»ΠΈΠΌΠΈΡ ΠΏΠΎ ΡΠΌΠΎΠ»ΡΠ°Π½ΠΈΡ',
+ ],
+ 'no devices' => [
+ 'en' => 'no devices',
+ 'ru' => 'Π½Π΅Ρ ΡΡΡΡΠΎΠΉΡΡΠ²',
+ ],
+ 'hwid notice' => [
+ 'en' => 'HWID limit works only when subscription is refreshed or added',
+ 'ru' => 'HWID Π»ΠΈΠΌΠΈΡ ΡΡΠ°Π±Π°ΡΡΠ²Π°Π΅Ρ ΡΠΎΠ»ΡΠΊΠΎ Π² ΠΌΠΎΠΌΠ΅Π½Ρ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΡ ΠΏΠΎΠ΄ΠΏΠΈΡΠΊΠΈ',
+ ],
'on' => [
'en' => 'π’',
'ru' => 'π’',
diff --git a/config/hwid.json b/config/hwid.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/config/hwid.json
@@ -0,0 +1 @@
+{}