This commit is contained in:
mrilyew 2024-08-07 15:15:42 +04:00 committed by GitHub
commit 676177e18d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 481 additions and 5 deletions

View file

@ -143,4 +143,30 @@ class Wall implements Handler
$resolve($arr); $resolve($arr);
} }
function getIgnoredSources(int $page = 1, callable $resolve, callable $reject)
{
$surses = $this->user->getIgnoredSources($page, 10);
$arr = [
"count" => $this->user->getIgnoredSourcesCount(),
"items" => []
];
foreach($surses as $surs) {
$arr["items"][] = [
"id" => $surs->getRealId(),
"name" => $surs->getCanonicalName(),
"additional" => (($surs->getRealId() > 0 ? $surs->getStatus() : $surs->getDescription()) ?? "..."),
"avatar" => $surs->getAvatarURL(),
"url" => $surs->getURL(),
];
}
if(rand(0, 200) == 50) {
$arr["fact"] = $this->user->getIgnoresCount();
}
$resolve($arr);
}
} }

View file

@ -1,9 +1,9 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace openvk\VKAPI\Handlers; namespace openvk\VKAPI\Handlers;
use Chandler\Database\DatabaseConnection; use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Posts as PostsRepo; use openvk\Web\Models\Repositories\Posts as PostsRepo, Users as UsersRepo, Clubs;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\VKAPI\Handlers\Wall; use openvk\VKAPI\Handlers\{Wall, Users, Groups};
final class Newsfeed extends VKAPIRequestHandler final class Newsfeed extends VKAPIRequestHandler
{ {
@ -47,7 +47,7 @@ final class Newsfeed extends VKAPIRequestHandler
return $response; return $response;
} }
function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0) function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $return_banned = 0)
{ {
$this->requireUser(); $this->requireUser();
@ -57,6 +57,12 @@ final class Newsfeed extends VKAPIRequestHandler
if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT) if($this->getUser()->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0"; $queryBase .= " AND `nsfw` = 0";
if(($ignoredCount = $this->getUser()->getIgnoredSourcesCount()) > 0 && $return_banned == 0) {
$sources = implode("', '", $this->getUser()->getIgnoredSources(1, $ignoredCount, true));
$queryBase .= " AND `posts`.`wall` NOT IN ('$sources')";
}
$start_from = empty($start_from) ? PHP_INT_MAX : $start_from; $start_from = empty($start_from) ? PHP_INT_MAX : $start_from;
$start_time = empty($start_time) ? 0 : $start_time; $start_time = empty($start_time) ? 0 : $start_time;
$end_time = empty($end_time) ? PHP_INT_MAX : $end_time; $end_time = empty($end_time) ? PHP_INT_MAX : $end_time;
@ -74,4 +80,130 @@ final class Newsfeed extends VKAPIRequestHandler
return $response; return $response;
} }
function getBanned(int $extended = 0, string $fields = "", string $name_case = "nom")
{
$this->requireUser();
$count = 50;
$offset = 0;
$banned = array_slice($this->getUser()->getIgnoredSources(1, $count + $offset, true), $offset);
if($extended == 0) {
$retArr/*d*/ = [
"groups" => [],
"members" => [] # why
];
foreach($banned as $ban) {
if($ban > 0) {
$retArr["members"][] = $ban;
} else {
$retArr["groups"][] = $ban;
}
}
return $retArr;
} else {
$retArr = [
"groups" => [],
"profiles" => []
];
$usIds = "";
$clubIds = "";
foreach($banned as $ban) {
if($ban > 0) {
$usIds .= $ban . ",";
} else {
$clubIds .= ($ban * -1) . ",";
}
}
$retArr["profiles"][] = (new Users)->get($usIds, $fields);
$retArr["groups"][] = (new Groups)->getById($clubIds, $fields);
return $retArr;
}
}
function addBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($user_ids) && empty($group_ids))
$this->fail(52, "Provide 'user_ids' or 'groups_ids'");
$arr = [];
if(!empty($user_ids)) {
$arr = array_merge($arr, array_map(function($el) {
return (int)$el;
}, explode(",", $user_ids)));
}
if(!empty($group_ids)) {
$arr = array_merge($arr, array_map(function($el) {
return abs((int)$el) * -1;
}, explode(",", $group_ids)));
}
$arr = array_unique($arr);
if(sizeof($arr) > 10 || sizeof($arr) < 1)
$this->fail(20, "You can ignore only 10 users/groups at once");
$successes = 0;
foreach($arr as $ar) {
$entity = getEntity($ar);
if(!$entity || $entity->isHideFromGlobalFeedEnabled() || $entity->isIgnoredBy($this->getUser())) continue;
$entity->toggleIgnore($this->getUser());
$successes += 1;
}
return (int)($successes > 0);
}
function deleteBan(string $user_ids = "", string $group_ids = "")
{
$this->requireUser();
$this->willExecuteWriteAction();
if(empty($user_ids) && empty($group_ids))
$this->fail(52, "Provide 'user_ids' or 'groups_ids'");
$arr = [];
if(!empty($user_ids)) {
$arr = array_merge($arr, array_map(function($el) {
return (int)$el;
}, explode(",", $user_ids)));
}
if(!empty($group_ids)) {
$arr = array_merge($arr, array_map(function($el) {
return abs((int)$el) * -1;
}, explode(",", $group_ids)));
}
$arr = array_unique($arr);
if(sizeof($arr) > 10 || sizeof($arr) < 1)
$this->fail(20, "You can unignore only 10 users/groups at once");
$successes = 0;
foreach($arr as $ar) {
$entity = getEntity($ar);
if(!$entity || $entity->isHideFromGlobalFeedEnabled() || !$entity->isIgnoredBy($this->getUser())) continue;
$entity->toggleIgnore($this->getUser());
$successes += 1;
}
return (int)($successes > 0);
}
} }

View file

@ -469,4 +469,5 @@ class Club extends RowModel
use Traits\TBackDrops; use Traits\TBackDrops;
use Traits\TSubscribable; use Traits\TSubscribable;
use Traits\TAudioStatuses; use Traits\TAudioStatuses;
use Traits\TIgnorable;
} }

View file

@ -0,0 +1,48 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities\Traits;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\User;
trait TIgnorable
{
function isIgnoredBy(User $user): bool
{
$ctx = DatabaseConnection::i()->getContext();
$data = [
"owner" => $user->getId(),
"ignored_source" => $this->getRealId(),
];
$sub = $ctx->table("ignored_sources")->where($data);
if(!$sub->fetch()) {
return false;
}
return true;
}
function getIgnoresCount()
{
return sizeof(DatabaseConnection::i()->getContext()->table("ignored_sources")->where("ignored_source", $this->getRealId()));
}
function toggleIgnore(User $user): bool
{
if($this->isIgnoredBy($user)) {
DatabaseConnection::i()->getContext()->table("ignored_sources")->where([
"owner" => $user->getId(),
"ignored_source" => $this->getRealId(),
])->delete();
return false;
} else {
DatabaseConnection::i()->getContext()->table("ignored_sources")->insert([
"owner" => $user->getId(),
"ignored_source" => $this->getRealId(),
]);
return true;
}
}
}

View file

@ -1339,6 +1339,36 @@ class User extends RowModel
return $res; return $res;
} }
function getIgnoredSources(int $page = 1, int $perPage = 10, bool $onlyIds = false)
{
$sources = DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->page($page, $perPage);
$arr = [];
foreach($sources as $source) {
$ignoredSource = (int)$source->ignored_source;
if($ignoredSource > 0)
$ignoredSourceModel = (new Users)->get($ignoredSource);
else
$ignoredSourceModel = (new Clubs)->get(abs($ignoredSource));
if(!$ignoredSourceModel)
continue;
if(!$onlyIds)
$arr[] = $ignoredSourceModel;
else
$arr[] = $ignoredSourceModel->getRealId();
}
return $arr;
}
function getIgnoredSourcesCount()
{
return sizeof(DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId()));
}
function getAudiosCollectionSize() function getAudiosCollectionSize()
{ {
return (new \openvk\Web\Models\Repositories\Audios)->getUserCollectionSize($this); return (new \openvk\Web\Models\Repositories\Audios)->getUserCollectionSize($this);
@ -1378,7 +1408,13 @@ class User extends RowModel
return $returnArr; return $returnArr;
} }
function isHideFromGlobalFeedEnabled(): bool
{
return $this->isClosed();
}
use Traits\TBackDrops; use Traits\TBackDrops;
use Traits\TSubscribable; use Traits\TSubscribable;
use Traits\TAudioStatuses; use Traits\TAudioStatuses;
use Traits\TIgnorable;
} }

View file

@ -197,6 +197,12 @@ final class WallPresenter extends OpenVKPresenter
if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT) if($this->user->identity->getNsfwTolerance() === User::NSFW_INTOLERANT)
$queryBase .= " AND `nsfw` = 0"; $queryBase .= " AND `nsfw` = 0";
if(($ignoredCount = $this->user->identity->getIgnoredSourcesCount()) > 0) {
$sources = implode("', '", $this->user->identity->getIgnoredSources(1, $ignoredCount, true));
$queryBase .= " AND `posts`.`wall` NOT IN ('$sources')";
}
$posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " ORDER BY `created` DESC LIMIT " . $pPage . " OFFSET " . ($page - 1) * $pPage); $posts = DatabaseConnection::i()->getConnection()->query("SELECT `posts`.`id` " . $queryBase . " ORDER BY `created` DESC LIMIT " . $pPage . " OFFSET " . ($page - 1) * $pPage);
$count = DatabaseConnection::i()->getConnection()->query("SELECT COUNT(*) " . $queryBase)->fetch()->{"COUNT(*)"}; $count = DatabaseConnection::i()->getConnection()->query("SELECT COUNT(*) " . $queryBase)->fetch()->{"COUNT(*)"};
@ -651,6 +657,60 @@ final class WallPresenter extends OpenVKPresenter
]]); ]]);
} }
function renderIgnoreSource()
{
$this->assertUserLoggedIn();
$this->willExecuteWriteAction(true);
if($_SERVER["REQUEST_METHOD"] !== "POST")
exit("");
$owner = $this->user->id;
$ignoredSource = (int)$this->postParam("source");
if($this->user->identity->getIgnoredSourcesCount() > 50)
$this->flashFail("err", "Error", tr("max_ignores", 50), null, true);
if($ignoredSource > 0) {
$ignoredSourceModel = (new Users)->get($ignoredSource);
if(!$ignoredSourceModel)
$this->flashFail("err", "Error", tr("invalid_user"), null, true);
if($ignoredSourceModel->getId() == $this->user->id)
$this->flashFail("err", "Error", tr("cant_ignore_self"), null, true);
if($ignoredSourceModel->isClosed())
$this->flashFail("err", "Error", tr("no_sense"), null, true);
} else {
$ignoredSourceModel = (new Clubs)->get(abs($ignoredSource));
if(!$ignoredSourceModel)
$this->flashFail("err", "Error", tr("invalid_club"), null, true);
if($ignoredSourceModel->isHideFromGlobalFeedEnabled())
$this->flashFail("err", "Error", tr("no_sense"), null, true);
}
if(!$ignoredSourceModel->toggleIgnore($this->user->identity)) {
$tr = "";
if($ignoredSource > 0)
$tr = tr("ignore_user");
else
$tr = tr("ignore_club");
$this->returnJson(["success" => true, "act" => "unignored", "text" => $tr]);
} else {
if($ignoredSource > 0)
$tr = tr("unignore_user");
else
$tr = tr("unignore_club");
$this->returnJson(["success" => true, "act" => "ignored", "text" => $tr]);
}
}
function renderAccept() { function renderAccept() {
$this->assertUserLoggedIn(); $this->assertUserLoggedIn();
$this->willExecuteWriteAction(true); $this->willExecuteWriteAction(true);

View file

@ -401,6 +401,7 @@
{ifset $thisUser} {ifset $thisUser}
{script "js/al_notifs.js"} {script "js/al_notifs.js"}
{script "js/al_feed.js"}
{/ifset} {/ifset}
{if OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']} {if OPENVK_ROOT_CONF['openvk']['preferences']['bellsAndWhistles']['fartscroll']}

View file

@ -175,6 +175,9 @@
{var $canReport = $thisUser->getId() != $club->getOwner()->getId()} {var $canReport = $thisUser->getId() != $club->getOwner()->getId()}
{if $canReport} {if $canReport}
<a class="profile_link" style="display:block;" href="javascript:reportVideo()">{_report}</a> <a class="profile_link" style="display:block;" href="javascript:reportVideo()">{_report}</a>
<a n:if="!$club->isHideFromGlobalFeedEnabled()" class="profile_link" style="display:block;" id="ignoreSomeone" data-id="-{$club->getId()}">
{if !$club->isIgnoredBy($thisUser)}{_ignore_club}{else}{_unignore_club}{/if}
</a>
<script> <script>
function reportVideo() { function reportVideo() {

View file

@ -171,6 +171,9 @@
{/if} {/if}
<a class="profile_link" style="display:block;width:96%;" href="javascript:reportUser()">{_report}</a> <a class="profile_link" style="display:block;width:96%;" href="javascript:reportUser()">{_report}</a>
<a class="profile_link" style="display:block;width:96%;" id="ignoreSomeone" data-id="{$user->getId()}">
{if !$user->isIgnoredBy($thisUser)}{_ignore_user}{else}{_unignore_user}{/if}
</a>
<script> <script>
function reportUser() { function reportUser() {
uReportMsgTxt = tr("going_to_report_user"); uReportMsgTxt = tr("going_to_report_user");

View file

@ -15,6 +15,8 @@
<div n:attr="id => (isset($globalFeed) ? 'activetabs' : 'ki')" class="tab"> <div n:attr="id => (isset($globalFeed) ? 'activetabs' : 'ki')" class="tab">
<a n:attr="id => (isset($globalFeed) ? 'act_tab_a' : 'ki')" href="/feed/all">{_all_news}</a> <a n:attr="id => (isset($globalFeed) ? 'act_tab_a' : 'ki')" href="/feed/all">{_all_news}</a>
</div> </div>
<span n:if="isset($globalFeed) && $thisUser->getIgnoredSourcesCount() > 0" id="_ignoredSourcesLink">{_ignored_sources}</span>
</div> </div>
<div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog"> <div n:class="postFeedWrapper, $thisUser->hasMicroblogEnabled() ? postFeedWrapperMicroblog">

View file

@ -141,6 +141,8 @@ routes:
handler: "Wall->delete" handler: "Wall->delete"
- url: "/wall{num}_{num}/pin" - url: "/wall{num}_{num}/pin"
handler: "Wall->pin" handler: "Wall->pin"
- url: "/wall/ignoreSource"
handler: "Wall->ignoreSource"
- url: "/wall/accept" - url: "/wall/accept"
handler: "Wall->accept" handler: "Wall->accept"
- url: "/wall/decline" - url: "/wall/decline"

View file

@ -3032,6 +3032,35 @@ body.article .floating_sidebar, body.article .page_content {
background: #E9F0F1 !important; background: #E9F0F1 !important;
} }
#_ignoredSourcesLink {
float: right;
margin-right: 3px;
margin-top: 2px;
color: #2B587A;
cursor: pointer;
}
#_ignoredSourcesLink:hover {
text-decoration: underline;
}
._ignorredList {
height: 87%;
border: 1px solid gray;
overflow-y: auto;
margin-top: 4px;
padding: 5px;
}
._ignorredList ._ignoredListContent img {
width: 38px;
}
._ignorredList ._ignoredListContent {
height: 42px;
padding-bottom: 9px;
}
.searchOptions.newer { .searchOptions.newer {
padding-left: 6px; padding-left: 6px;
border-top: unset !important; border-top: unset !important;
@ -3069,4 +3098,3 @@ hr {
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
background-position: 50% !important; background-position: 50% !important;
} }

80
Web/static/js/al_feed.js Normal file
View file

@ -0,0 +1,80 @@
$(document).on("click", "#_ignoredSourcesLink", (e) => {
let body = `
<span id="ignoredClubersList">${tr("ignored_clubsers_list")}</span>
<div class="_ignorredList"></div>
`
MessageBox(tr("ignored_sources"), body, [tr("cancel")], [Function.noop]);
document.querySelector(".ovk-diag-body").style.padding = "10px"
document.querySelector(".ovk-diag-body").style.height = "330px"
async function insertMoreSources(page) {
document.querySelector("._ignorredList").insertAdjacentHTML("beforeend", `<img id="loader" src="/assets/packages/static/openvk/img/loading_mini.gif">`)
let ar = await API.Wall.getIgnoredSources(page)
u("#loader").remove()
let pagesCount = Math.ceil(Number(ar.count) / 10)
for(const a of ar.items) {
document.querySelector("._ignorredList").insertAdjacentHTML("beforeend", `
<div class="_ignoredListContent">
<a href="${a.url}" target="_blank">
<img style="float: left" class="ava" src="${a.avatar}">
</a>
<div style="float: left;margin-left: 6px;">
<a href="${a.url}" target="_blank">${ovk_proc_strtr(escapeHtml(a.name), 30)}</a><br>
<a class="profile_link" id="ignoreSomeone" data-id="${a.id}">${a.id > 0 ? tr("unignore_user") : tr("unignore_club")}</a>
</div>
</div>
`)
}
if(ar.fact && document.querySelector("#ignoredClubersList").dataset.fact != 1) {
document.querySelector("#ignoredClubersList").innerHTML += " "+tr("interesting_fact", Number(ar.fact))
document.querySelector("#ignoredClubersList").setAttribute("data-fact", "1")
}
if(page < pagesCount) {
document.querySelector("._ignorredList").insertAdjacentHTML("beforeend", `
<div id="showMoreIgnors" data-pagesCount="${pagesCount}" data-page="${page + 1}" style="width: 99%;text-align: center;background: #d5d5d5;height: 22px;padding-top: 9px;cursor:pointer;">
<span>more...</span>
</div>`)
}
}
insertMoreSources(1)
$(".ignorredList .list").on("click", "#showMoreIgnors", (e) => {
u(e.currentTarget).remove()
insertMoreSources(Number(e.currentTarget.dataset.page))
})
})
$(document).on("click", "#ignoreSomeone", (e) => {
let xhr = new XMLHttpRequest()
xhr.open("POST", "/wall/ignoreSource")
xhr.onloadstart = () => {
e.currentTarget.classList.add("lagged")
}
xhr.onerror = xhr.ontimeout = () => {
MessageBox(tr("error"), "Unknown error occured", [tr("ok")], [Function.noop]);
}
xhr.onload = () => {
let result = JSON.parse(xhr.responseText)
e.currentTarget.classList.remove("lagged")
if(result.success) {
e.currentTarget.innerHTML = result.text
} else {
MessageBox(tr("error"), result.flash.message, [tr("ok")], [Function.noop]);
}
}
let formdata = new FormData
formdata.append("hash", u("meta[name=csrf]").attr("value"))
formdata.append("source", e.currentTarget.dataset.id)
xhr.send(formdata)
})

View file

@ -277,6 +277,13 @@ function parseAttachments(string $attachments): array
return $returnArr; return $returnArr;
} }
function getEntity(int $id) {
if($id > 0)
return (new openvk\Web\Models\Repositories\Users)->get($id);
return (new openvk\Web\Models\Repositories\Clubs)->get(abs($id));
}
function ovk_scheme(bool $with_slashes = false): string function ovk_scheme(bool $with_slashes = false): string
{ {
$scheme = ovk_is_ssl() ? "https" : "http"; $scheme = ovk_is_ssl() ? "https" : "http";

View file

@ -0,0 +1,6 @@
CREATE TABLE `ignored_sources` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`owner` bigint(20) UNSIGNED NOT NULL,
`ignored_source` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

View file

@ -245,6 +245,21 @@
"edited_short" = "edited"; "edited_short" = "edited";
"ignored_sources" = "Ignored sources";
"ignore_user" = "Ignore user";
"unignore_user" = "Stop ignoring user";
"ignore_club" = "Ignore club";
"unignore_club" = "Stop ignoring club";
"ignored_clubsers_list" = "This users and clubs doesn't appears in your global feed.";
"interesting_fact_zero" = "Fun fact: you doesn't have been ignored!";
"interesting_fact_one" = "Fun fact: you have been ignored by one user";
"interesting_fact_few" = "Fun fact: you have been ignored by $1 users.";
"interesting_fact_many" = "Fun fact: you have been ignored by $1 users.";
"interesting_fact_other" = "Fun fact: you have been ignored by $1 users.";
"add_new_ignores" = "Add new";
"all_posts" = "All posts"; "all_posts" = "All posts";
"users_posts" = "Posts by $1"; "users_posts" = "Posts by $1";
"clubs_posts" = "Group's posts"; "clubs_posts" = "Group's posts";
@ -1516,6 +1531,11 @@
"group_is_banned" = "Group was successfully banned"; "group_is_banned" = "Group was successfully banned";
"description_too_long" = "Description is too long."; "description_too_long" = "Description is too long.";
"invalid_club" = "This group does not exists.";
"invalid_user" = "This user does not exists.";
"no_sense" = "No sense.";
"cant_ignore_self" = "Can't ignore urself.";
"max_ignores" = "Max ignores — $1";
"invalid_audio" = "Invalid audio."; "invalid_audio" = "Invalid audio.";
"do_not_have_audio" = "You don't have this audio."; "do_not_have_audio" = "You don't have this audio.";

View file

@ -225,6 +225,20 @@
"post_is_ad" = "Этот пост был размещён за взятку."; "post_is_ad" = "Этот пост был размещён за взятку.";
"edited_short" = "ред."; "edited_short" = "ред.";
"ignored_sources" = "Игнорируемые источники";
"ignore_user" = "Игнорировать пользователя";
"unignore_user" = "Не игнорировать пользователя";
"ignore_club" = "Игнорировать группу";
"unignore_club" = "Не игнорировать группу";
"ignored_clubsers_list" = "Эти пользователи и группы не показываются в глобальной ленте.";
"interesting_fact_zero" = "Интересный факт: вас никто не игнорирует!";
"interesting_fact_one" = "Интересный факт: вас игнорирует один пользователь.";
"interesting_fact_few" = "Интересный факт: вас игнорирует $1 пользователя.";
"interesting_fact_many" = "Интересный факт: вас игнорирует $1 пользователей.";
"interesting_fact_other" = "Интересный факт: вас игнорирует $1 пользователей.";
"add_new_ignores" = "Добавить новое";
"all_posts" = "Все записи"; "all_posts" = "Все записи";
"users_posts" = "Записи $1"; "users_posts" = "Записи $1";
"clubs_posts" = "Записи сообщества"; "clubs_posts" = "Записи сообщества";
@ -1419,6 +1433,13 @@
"group_owner_is_banned" = "Создатель сообщества успешно забанен."; "group_owner_is_banned" = "Создатель сообщества успешно забанен.";
"group_is_banned" = "Сообщество успешно забанено"; "group_is_banned" = "Сообщество успешно забанено";
"description_too_long" = "Описание слишком длинное."; "description_too_long" = "Описание слишком длинное.";
"invalid_club" = "Такой группы не существует.";
"invalid_user" = "Такого пользователя не существует.";
"no_sense" = "Нет смысла.";
"cant_ignore_self" = "Нельзя игнорировать себя.";
"max_ignores" = "Максимальное число игноров — $1";
"invalid_audio" = "Такой аудиозаписи не существует."; "invalid_audio" = "Такой аудиозаписи не существует.";
"do_not_have_audio" = "У вас нет этой аудиозаписи."; "do_not_have_audio" = "У вас нет этой аудиозаписи.";
"do_have_audio" = "У вас уже есть эта аудиозапись."; "do_have_audio" = "У вас уже есть эта аудиозапись.";