This commit is contained in:
n1rwana 2023-08-08 18:52:25 +03:00
parent ecde2fd5d6
commit e27cda1f08
No known key found for this signature in database
GPG key ID: 1D319A83686EC843
12 changed files with 778 additions and 13 deletions

View file

@ -361,6 +361,12 @@ class Club extends RowModel
$this->save(); $this->save();
} }
function unban(): void
{
$this->setBlock_Reason(null);
$this->save();
}
function getAlert(): ?string function getAlert(): ?string
{ {
return $this->getRecord()->alert; return $this->getRecord()->alert;

View file

@ -0,0 +1,71 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
use openvk\Web\Models\Repositories\{Users};
use Nette\Database\Table\ActiveRow;
class NoSpamLog extends RowModel
{
protected $tableName = "noSpam_templates";
function getId(): int
{
return $this->getRecord()->id;
}
function getUser(): ?User
{
return (new Users)->get($this->getRecord()->user);
}
function getModel(): string
{
return $this->getRecord()->model;
}
function getRegex(): ?string
{
return $this->getRecord()->regex;
}
function getRequest(): ?string
{
return $this->getRecord()->request;
}
function getCount(): int
{
return $this->getRecord()->count;
}
function getTime(): DateTime
{
return new DateTime($this->getRecord()->time);
}
function getItems(): ?array
{
return explode(",", $this->getRecord()->items);
}
function getTypeRaw(): int
{
return $this->getRecord()->ban_type;
}
function getType(): string
{
switch ($this->getTypeRaw()) {
case 1: return "О";
case 2: return "Б";
case 3: return "ОБ";
default: return (string) $this->getTypeRaw();
}
}
function isRollbacked(): bool
{
return !is_null($this->getRecord()->rollback);
}
}

View file

@ -264,12 +264,15 @@ class User extends RowModel
$reason = $ban->getReason(); $reason = $ban->getReason();
preg_match('/\*\*content-(post|photo|video|group|comment|note|app)-(\d+)\*\*$/', $reason, $matches); preg_match('/\*\*content-(post|photo|video|group|comment|note|app|noSpamTemplate)-(\d+)\*\*$/', $reason, $matches);
if (sizeof($matches) === 3) { if (sizeof($matches) === 3) {
$content_type = $matches[1]; $content_id = (int) $matches[2];
if ($content_type === "noSpamTemplate") {
$reason = "Подозрительная активность";
} else {
if ($for !== "banned") { if ($for !== "banned") {
$reason = "Подозрительная активность"; $reason = "Подозрительная активность";
} else { } else {
$content_type = $matches[1]; $content_id = (int) $matches[2];
$reason = [$this->getTextForContentBan($content_type), $content_type]; $reason = [$this->getTextForContentBan($content_type), $content_type];
switch ($content_type) { switch ($content_type) {
case "post": $reason[] = (new Posts)->get($content_id); break; case "post": $reason[] = (new Posts)->get($content_id); break;
@ -283,6 +286,7 @@ class User extends RowModel
} }
} }
} }
}
return $reason; return $reason;
} }
@ -902,6 +906,21 @@ class User extends RowModel
$this->save(); $this->save();
} }
function unban(int $removed_by): void
{
$ban = (new Bans)->get((int) $this->getRawBanReason());
if (!$ban || $ban->isOver())
return;
$ban->setRemoved_Manually(true);
$ban->setRemoved_By($removed_by);
$ban->save();
$this->setBlock_Reason(NULL);
// $user->setUnblock_time(NULL);
$this->save();
}
function deactivate(?string $reason): void function deactivate(?string $reason): void
{ {
$this->setDeleted(1); $this->setDeleted(1);

View file

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class NoSpamLogs
{
private $context;
private $noSpamLogs;
function __construct()
{
$this->context = DatabaseConnection::i()->getContext();
$this->noSpamLogs = $this->context->table("noSpam_templates");
}
private function toNoSpamLog(?ActiveRow $ar): ?NoSpamLog
{
return is_null($ar) ? NULL : new NoSpamLog($ar);
}
function get(int $id): ?NoSpamLog
{
return $this->toNoSpamLog($this->noSpamLogs->get($id));
}
function getList(array $filter = []): \Traversable
{
foreach ($this->noSpamLogs->where($filter)->order("`id` DESC") as $log)
yield new NoSpamLog($log);
}
}

View file

@ -0,0 +1,276 @@
<?php declare(strict_types=1);
namespace openvk\Web\Presenters;
use Nette\Database\DriverException;
use Nette\Utils\Finder;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\Club;
use openvk\Web\Models\Entities\Comment;
use openvk\Web\Models\Entities\NoSpamLog;
use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Repositories\NoSpamLogs;
final class NoSpamPresenter extends OpenVKPresenter
{
protected $banTolerant = true;
protected $deactivationTolerant = true;
protected $presenterName = "nospam";
const ENTITIES_NAMESPACE = "openvk\\Web\\Models\\Entities";
function __construct()
{
parent::__construct();
}
function renderIndex(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$targetDir = __DIR__ . '/../Models/Entities/';
$mode = in_array($this->queryParam("act"), ["form", "templates", "rollback"]) ? $this->queryParam("act") : "form";
if ($mode === "form") {
$this->template->_template = "NoSpam/Index";
$foundClasses = [];
foreach (Finder::findFiles('*.php')->from($targetDir) as $file) {
$content = file_get_contents($file->getPathname());
$namespacePattern = '/namespace\s+([^\s;]+)/';
$classPattern = '/class\s+([^\s{]+)/';
preg_match($namespacePattern, $content, $namespaceMatches);
preg_match($classPattern, $content, $classMatches);
if (isset($namespaceMatches[1]) && isset($classMatches[1])) {
$classNamespace = trim($namespaceMatches[1]);
$className = trim($classMatches[1]);
$fullClassName = $classNamespace . '\\' . $className;
if ($classNamespace === NoSpamPresenter::ENTITIES_NAMESPACE && class_exists($fullClassName)) {
$foundClasses[] = $className;
}
}
}
$models = [];
foreach ($foundClasses as $class) {
$r = new \ReflectionClass(NoSpamPresenter::ENTITIES_NAMESPACE . "\\$class");
if (!$r->isAbstract() && $r->getName() !== NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
$models[] = $class;
}
$this->template->models = $models;
} else if ($mode === "templates") {
$this->template->_template = "NoSpam/Templates.xml";
$filter = [];
if ($this->queryParam("id")) {
$filter["id"] = (int) $this->queryParam("id");
}
$this->template->templates = iterator_to_array((new NoSpamLogs)->getList($filter));
} else {
$template = (new NoSpamLogs)->get((int) $this->postParam("id"));
if (!$template || $template->isRollbacked())
$this->returnJson(["success" => false, "error" => "Шаблон не найден"]);
$model = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $template->getModel();
$items = $template->getItems();
if (count($items) > 0) {
$db = DatabaseConnection::i()->getContext();
$unbanned_ids = [];
foreach ($items as $_item) {
try {
$item = new $model;
$table_name = $item->getTableName();
$item = $db->table($table_name)->get((int) $_item);
if (!$item) continue;
$item = new $model($item);
if (key_exists("deleted", $item->unwrap()) && $item->isDeleted()) {
$item->setDeleted(0);
$item->save();
}
if (in_array($template->getTypeRaw(), [2, 3])) {
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($item, "ban")) {
$owner = $item;
} else {
foreach ($methods as $method) {
if (method_exists($item, $method)) {
$owner = $item->$method();
break;
}
}
}
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $unbanned_ids)) {
$owner->unban($this->user->id);
$unbanned_ids[] = $_id;
}
}
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
} else {
$this->returnJson(["success" => false, "error" => "Объекты не найдены"]);
}
$template->setRollback(true);
$template->save();
$this->returnJson(["success" => true]);
}
}
function renderSearch(): void
{
$this->assertUserLoggedIn();
$this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0);
$this->assertNoCSRF();
$this->willExecuteWriteAction();
try {
$where = $this->postParam("where");
$ip = $this->postParam("ip");
$useragent = $this->postParam("useragent");
$searchTerm = $this->postParam("q");
$ts = $this->postParam("ts");
$te = $this->postParam("te");
if (!$ip && !$useragent && !$searchTerm && !$ts && !$te && !$where && !$searchTerm)
$this->returnJson(["success" => false, "error" => "Нет запроса. Заполните поле \"подстрока\" или введите запрос \"WHERE\" в поле под ним."]);
$model_name = NoSpamPresenter::ENTITIES_NAMESPACE . "\\" . $this->postParam("model");
if (!class_exists($model_name))
$this->returnJson(["success" => false, "error" => "Модель не найдена"]);
$model = new $model_name;
$c = new \ReflectionClass($model_name);
if ($c->isAbstract() || $c->getName() == NoSpamPresenter::ENTITIES_NAMESPACE . "\\Correspondence")
$this->returnJson(["success" => false, "error" => "No."]);
$db = DatabaseConnection::i()->getContext();
$table = $model->getTableName();
$columns = $db->getStructure()->getColumns($table);
$rows = [];
if (!$where) {
$conditions = [];
$need_deleted = false;
foreach ($columns as $column) {
if ($column["name"] == "deleted") {
$need_deleted = true;
} else {
$conditions[] = "`$column[name]` REGEXP '$searchTerm'";
}
}
$where = "(" . implode(" OR ", $conditions) . ")";
if ($need_deleted) $where .= " AND `deleted` = 0";
}
$result = $db->query("SELECT * FROM `$table` WHERE $where");
$rows = $result->fetchAll();
if (!in_array((int)$this->postParam("ban"), [1, 2, 3])) {
$response = [];
foreach ($rows as $key => $object) {
$object = (array)$object;
$_obj = [];
foreach ($object as $key => $value) {
foreach ($columns as $column) {
if ($column["name"] === $key && in_array(strtoupper($column["nativetype"]), ["BLOB", "BINARY", "VARBINARY", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB"])) {
$value = "[BINARY]";
break;
}
}
$_obj[$key] = $value;
}
$response[] = $_obj;
}
$this->returnJson(["success" => true, "count" => count($response), "list" => $response]);
} else {
$ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$ids[] = $object->getId();
}
$log = new NoSpamLog;
$log->setUser($this->user->id);
$log->setModel($this->postParam("model"));
if ($searchTerm) {
$log->setRegex($searchTerm);
} else {
$log->setRequest($where);
}
$log->setBan_Type((int) $this->postParam("ban"));
$log->setCount(count($rows));
$log->setTime(time());
$log->setItems(implode(",", $ids));
$log->save();
$banned_ids = [];
foreach ($rows as $object) {
$object = new $model_name($db->table($table)->get($object->id));
if (!$object) continue;
$owner = NULL;
$methods = ["getOwner", "getUser", "getRecipient", "getInitiator"];
if (method_exists($object, "ban")) {
$owner = $object;
} else {
foreach ($methods as $method) {
if (method_exists($object, $method)) {
$owner = $object->$method();
break;
}
}
}
if ($owner instanceof User && $owner->getId() === $this->user->id) {
if (count($rows) === 1) {
$this->returnJson(["success" => false, "error" => "\"Производственная травма\" — Вы не можете блокировать или удалять свой же контент"]);
} else {
continue;
}
}
if (in_array((int)$this->postParam("ban"), [2, 3])) {
if ($owner) {
$_id = ($owner instanceof Club ? $owner->getId() * -1 : $owner->getId());
if (!in_array($_id, $banned_ids)) {
if ($owner instanceof User) {
$owner->ban("**content-noSpamTemplate-" . $log->getId() . "**", false, time() + $owner->getNewBanTime(), $this->user->id);
} else {
$owner->ban("Подозрительная активность");
}
$banned_ids[] = $_id;
}
}
}
if (in_array((int)$this->postParam("ban"), [1, 3]))
$object->delete();
}
$this->returnJson(["success" => true]);
}
} catch (\Throwable $e) {
$this->returnJson(["success" => false, "error" => $e->getMessage()]);
}
}
}

View file

@ -214,6 +214,9 @@
(<b>{$reportNotAnsweredCount}</b>) (<b>{$reportNotAnsweredCount}</b>)
{/if} {/if}
</a> </a>
<a n:if="$canAccessHelpdesk" href="/noSpam" class="link">
noSpam
</a>
<a <a
n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem" n:foreach="OPENVK_ROOT_CONF['openvk']['preferences']['menu']['links'] as $menuItem"
href="{$menuItem['url']}" href="{$menuItem['url']}"

View file

@ -0,0 +1,193 @@
{extends "../@layout.xml"}
{block title}noSpam{/block}
{block header}{include title}{/block}
{block content}
{include "Tabs.xml", mode => "form"}
<br />
<div style="display: flex; border: 1px solid #ECECEC; padding: 8px;">
<div id="noSpam-form" style="width: 50%; border-right: 1px solid #ECECEC;">
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody>
<tr>
<td width="83px">
<span class="nobold">Раздел:</span>
</td>
<td>
<select name="model" id="model" style="margin-left: -2px;">
<option selected>Не выбрано</option>
<option n:foreach="$models as $model" value="{$model}">
{$model}
</option>
</select>
</td>
</tr>
</tbody>
</table>
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody>
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
<td>
<span class="nobold">Подстрока:</span>
</td>
<td>
<input type="text" name="regex" placeholder="Regex" id="regex">
</td>
</tr>
{* "Это могли бы быть мы с тобой, но" в овк нет логов :( *}
{*<tr style="width: 129px">
<td>
<span class="nobold">IP:</span>
</td>
<td>
<input type="text" name="ip" placeholder="или подсеть">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">Юзер-агент:</span>
</td>
<td>
<input type="text" name="useragent" placeholder="Mozila 1.0 Blablabla/test">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">Время раньше, чем:</span>
</td>
<td>
<input type="date" name="ds">
</td>
</tr>
<tr style="width: 129px">
<td>
<span class="nobold">Время позже, чем:</span>
</td>
<td>
<input type="date" name="de">
</td>
</tr>*}
</tbody>
</table>
<br />
<center><b>ИЛИ</b></center>
<br />
<textarea style="resize: vertical; width: calc(100% - 6px)" placeholder='city = "Воскресенск" && id = 1' name="where" id="where" />
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
<table cellspacing="7" cellpadding="0" width="100%" border="0">
<tbody>
<tr style="width: 129px; border-top: 1px solid #ECECEC;">
<td>
<span class="nobold">Параметры блокировки:</span>
</td>
<td>
<select name="ban_type" id="noSpam-ban-type">
<option value="1">Только откат</option>
<option value="2">Только блокировка</option>
<option value="3">Откат и блокировка</option>
</select>
</td>
</tr>
</tbody>
</table>
<div style="border-top: 1px solid #ECECEC; margin: 8px 0;" />
<center>
<div id="noSpam-buttons">
<input id="search" type="submit" value="Поиск" class="button" />
<input id="apply" type="submit" value="Применить" class="button" style="display: none;" />
</div>
<div id="noSpam-loader" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</div>
</center>
</div>
<div style="width: 50%;">
<center id="noSpam-results-loader" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px; margin: 125px 0;">
</center>
<center id="noSpam-results-text" style="margin: 125px 25px;">Здесь будут отображаться результаты поиска</center>
<div id="noSpam-results-block" style="display: none;">
<h4 style="padding: 8px;">Результаты поиска (<span id="noSpam-results-count" style="color: inherit; font-weight: inherit;"></span> шт.)</h4>
<ul style="padding-inline-start:18px;" id="noSpam-results-list"></ul>
</div>
</div>
</div>
<script>
async function search(ban = false) {
$("#noSpam-results-text").hide();
$("#noSpam-results-block").hide();
$("#apply").hide();
$("#noSpam-buttons").hide();
$("#noSpam-results-loader").show();
$("#noSpam-loader").show();
let model = $("#model").val();
let regex = $("#regex").val();
let where = $("#where").val();
await $.ajax({
type: "POST",
url: "/al_abuse/search",
data: {
model: model,
q: regex,
where: where,
ban: ban,
hash: {=$csrfToken}
},
success: (response) => {
if (response.success) {
console.log(response);
if (response.count > 0) {
$("#noSpam-results-list").empty();
$("#noSpam-results-count").text(response.count);
response.list.forEach((item) => {
const HTML_TAGS_REGEX = /<\/?([^>]+)(>|$)/g;
let fields = "";
Object.entries(item).map(([key, value]) => {
fields += `<b>${ key}</b>: ${ value?.toString()?.replace(HTML_TAGS_REGEX, "[$1]")}<br />`;
});
$("#noSpam-results-list").append(`<li>
<a style="display: block;" onClick="$('#noSpam-result-fields-${ model}-${ item.id}').toggle()">
<h4 style="display: inherit; padding: 8px;">${ model} #${ item.id}</h4>
</a>
<div style="display: none;" id="noSpam-result-fields-${ model}-${ item.id}">${ fields}</div>
</li>`);
});
$("#noSpam-results-block").show();
$("#apply").show();
} else {
$("#noSpam-results-text").text(ban ? "Операция завершена успешно" : "Ничего не найдено :(");
$("#noSpam-results-text").show();
}
} else {
$("#noSpam-results-text").text(response?.error ?? "Неизвестная ошибка");
$("#noSpam-results-text").show();
}
},
error: (error) => {
console.error("Error while searching noSpam:", error);
$("#noSpam-results-text").text("Ошибка при выполнении запроса");
$("#noSpam-results-text").show();
}
});
$("#noSpam-buttons").show();
$("#noSpam-loader").hide();
$("#noSpam-results-loader").hide();
}
$("#search").on("click", () => { search(); });
$("#regex, #where").keypress((e) => {
if (e.which === 13 && !e.shiftKey) {
e.preventDefault();
search();
}
});
$("#apply").on("click", () => { search(Number($("#noSpam-ban-type").val())); })
</script>
{/block}

View file

@ -0,0 +1,8 @@
<div class="tabs">
<div n:attr="id => ($mode === 'form' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($mode === 'form' ? 'act_tab_a' : 'ki')" href="/noSpam">Бан по шаблону</a>
</div>
<div n:attr="id => ($mode === 'templates' ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => ($mode === 'templates' ? 'act_tab_a' : 'ki')" href="/noSpam?act=templates">Шаблоны</a>
</div>
</div>

View file

@ -0,0 +1,129 @@
{extends "../@layout.xml"}
{block title}Шаблоны{/block}
{block header}{include title}{/block}
{block content}
{include "Tabs.xml", mode => "templates"}
<style>
table, th, td {
border: 1px solid #ECECEC;
border-collapse: collapse;
border-spacing: 0;
font-family: -apple-system, system-ui, "Helvetica Neue", Roboto, sans-serif;
}
table {
width: 100%;
}
td, th {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 90px;
}
tr:nth-child(odd) {
background-color: #f0f2f5;
}
tr:hover, th:hover {
background-color: #E8EBEF;
}
th {
text-transform: uppercase;
font-size: 0.846em;
color: #626d7a;
}
</style>
<br />
<div>
<table n:if="count($templates) > 0" cellspacing="0" cellpadding="7" width="100%">
<tr>
<th style="text-align: center;">ID</th>
<th>Пользователь</th>
<th style="text-align: center;">Раздел</th>
<th>Подстрока</th>
<th>Where</th>
<th style="text-align: center;">Тип</th>
<th style="text-align: center;">Количество</th>
<th>Время</th>
<th style="text-align: center;">Действия</th>
</tr>
<tr n:foreach="$templates as $template">
<td id="id-{$template->getId()}" onClick="openTableField('id', {$template->getId()})" style="text-align: center;"><b>{$template->getId()}</b></td>
<td id="user-{$template->getId()}" onClick="openTableField('user', {$template->getId()})">
<a href="{$template->getUser()->getURL()}" target="_blank">{$template->getUser()->getCanonicalName()}</a>
</td>
<td id="model-{$template->getId()}" onClick="openTableField('model', {$template->getId()})" style="text-align: center;">{$template->getModel()}</td>
<td id="regex-{$template->getId()}" onClick="openTableField('regex', {$template->getId()})">
<a>{$template->getRegex() ?? "-"}</a>
</td>
<td id="where-{$template->getId()}" onClick="openTableField('where', {$template->getId()})">
<a>{$template->getRequest() ?? "-"}</a>
</td>
<td id="type-{$template->getId()}" onClick="openTableField('type', {$template->getId()})" style="text-align: center;">{$template->getType()}</td>
<td id="count-{$template->getId()}" onClick="openTableField('count', {$template->getId()})" style="text-align: center;">
{$template->getCount()}
</td>
<td id="time-{$template->getId()}" onClick="openTableField('time', {$template->getId()})">{$template->getTime()}</td>
<td style="text-align: center;">
<div id="noSpam-rollback-{$template->getId()}">
<div id="noSpam-rollback-loader-{$template->getId()}" style="display: none;">
<img src="/assets/packages/static/openvk/img/loading_mini.gif" style="width: 40px;">
</div>
<a n:if="!$template->isRollbacked()" id="noSpam-rollback-template-link-{$template->getId()}" onClick="rollbackTemplate({$template->getId()})">откатить</a>
<span n:attr="style => $template->isRollbacked() ? '' : 'display: none;'" id="noSpam-rollback-template-rollbacked-{$template->getId()}">откачен</span>
</div>
</td>
</tr>
</table>
<div n:if="count($templates) <= 0">
{include "../components/nothing.xml"}
</div>
</div>
<script>
// Full width block
$(".navigation").hide();
$(".page_content").width("100%");
$(".page_body").width("100%").css("margin-right", 0).css("margin-top", "-5px");
$(".tabs").width("100%");
function openTableField(name, id) {
MessageBox(name, $(`#${ name}-${ id}`).text(), ["OK"], [Function.noop]);
}
async function rollbackTemplate(id) {
$(`#noSpam-rollback-template-link-${ id}`).hide();
$(`#noSpam-rollback-template-rollbacked-${ id}`).hide();
$(`#noSpam-rollback-loader-${ id}`).show();
await $.ajax({
type: "POST",
url: "/noSpam?act=rollback",
data: {
id: id,
hash: {=$csrfToken}
},
success: (response) => {
$(`#noSpam-rollback-loader-${ id}`).hide();
if (response.success) {
$(`#noSpam-rollback-template-rollbacked-${ id}`).show();
} else {
NewNotification("Ошибка", (response?.error ?? "Неизвестная ошибка"), "/assets/packages/static/openvk/img/error.png");
$(`#noSpam-rollback-template-link-${ id}`).show();
}
},
error: (error) => {
console.error(error);
NewNotification("Ошибка", "Ошибка при отправке запроса", "/assets/packages/static/openvk/img/error.png");
$(`#noSpam-rollback-loader-${ id}`).hide();
$(`#noSpam-rollback-template-link-${ id}`).show();
}
});
}
</script>
{/block}

View file

@ -51,3 +51,4 @@ services:
- openvk\Web\Models\Repositories\BannedLinks - openvk\Web\Models\Repositories\BannedLinks
- openvk\Web\Models\Repositories\ChandlerGroups - openvk\Web\Models\Repositories\ChandlerGroups
- openvk\Web\Presenters\MaintenancePresenter - openvk\Web\Presenters\MaintenancePresenter
- openvk\Web\Presenters\NoSpamPresenter

View file

@ -353,6 +353,10 @@ routes:
handler: "Admin->chandlerGroup" handler: "Admin->chandlerGroup"
- url: "/admin/chandler/users/{slug}" - url: "/admin/chandler/users/{slug}"
handler: "Admin->chandlerUser" handler: "Admin->chandlerUser"
- url: "/noSpam"
handler: "NoSpam->index"
- url: "/al_abuse/search"
handler: "NoSpam->search"
- url: "/internal/wall{num}" - url: "/internal/wall{num}"
handler: "Wall->wallEmbedded" handler: "Wall->wallEmbedded"
- url: "/robots.txt" - url: "/robots.txt"

View file

@ -0,0 +1,21 @@
CREATE TABLE `noSpam_templates`
(
`id` bigint(20) UNSIGNED NOT NULL,
`user` bigint(20) UNSIGNED NOT NULL,
`model` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`regex` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`request` longtext COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`ban_type` tinyint(4) NOT NULL,
`count` bigint(20) NOT NULL,
`time` bigint(20) UNSIGNED NOT NULL,
`items` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`rollback` tinyint(1) DEFAULT NULL
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
ALTER TABLE `noSpam_templates`
ADD PRIMARY KEY (`id`);
ALTER TABLE `noSpam_templates`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT;