Compare commits
54 commits
4109dfe8c3
...
d2fb1c0fd2
Author | SHA1 | Date | |
---|---|---|---|
|
d2fb1c0fd2 | ||
|
725e68d0e5 | ||
|
27fb0f1d90 | ||
|
4a366b987a | ||
|
62b93a3cf2 | ||
|
252ff0ead0 | ||
|
0ceac912ef | ||
|
2f8612bb24 | ||
|
b89bbdcec2 | ||
|
34b585747f | ||
|
027a38a69c | ||
|
770e73fc72 | ||
|
1a56b09430 | ||
|
940da6eb99 | ||
|
7cf4082691 | ||
|
eb439b278c | ||
|
2ffa4fd916 | ||
|
05a423b30b | ||
|
a3db17fc36 | ||
|
866d6a8c45 | ||
|
543c46696d | ||
|
299e5c6354 | ||
|
04444c8354 | ||
|
4a5f1f0019 | ||
|
6abdb0d593 | ||
|
56fe715bce | ||
|
185c007b50 | ||
|
699996210d | ||
|
9d6e81f990 | ||
|
54092eb6c0 | ||
|
0da679fae6 | ||
|
668d4f2ada | ||
|
cee1b4c8c1 | ||
|
cf558d57c5 | ||
|
0f2a88aa68 | ||
|
01bd8f938c | ||
|
cbec4b549f | ||
|
7ed870c3c6 | ||
|
c6c8e8e175 | ||
|
06b77ebad8 | ||
|
43e18a9173 | ||
|
700dcc121d | ||
|
aca48e726e | ||
|
beb8b4eaa6 | ||
|
5401871b38 | ||
|
c6a77de234 | ||
|
4845f8f318 | ||
|
934bc9b25c | ||
|
344ba53acd | ||
|
5436656aed | ||
|
db433b627f | ||
|
4cb8d2d69d | ||
|
6d26269873 | ||
|
63f302b2ab |
6
.github/workflows/codeberg-mirror.yml
vendored
|
@ -1,8 +1,6 @@
|
|||
name: Codeberg Mirroring
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
to_codeberg:
|
||||
|
@ -14,4 +12,4 @@ jobs:
|
|||
- uses: pixta-dev/repository-mirroring-action@v1
|
||||
with:
|
||||
target_repo_url: "git@codeberg.org:openvk/openvk.git"
|
||||
ssh_private_key: ${{ secrets.CODEBERG_PRIVSSH }}
|
||||
ssh_private_key: ${{ secrets.CODEBERG_MIRRORSSH }}
|
||||
|
|
2
.gitignore
vendored
|
@ -14,3 +14,5 @@ themepacks/*
|
|||
!themepacks/midnight
|
||||
storage/*
|
||||
!storage/.gitkeep
|
||||
|
||||
.idea
|
34
README.md
|
@ -2,29 +2,25 @@
|
|||
|
||||
_[Русский](README_RU.md)_
|
||||
|
||||
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VK. Code provided here is not stable yet.
|
||||
**OpenVK** is an attempt to create a simple CMS that ~~cosplays~~ imitates old VKontakte. Code provided here is not stable yet.
|
||||
|
||||
VKontakte belongs to Pavel Durov and VK Group.
|
||||
|
||||
To be honest, we don't know whether it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug-tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OVK account for this).
|
||||
To be honest, we don't know whether if it even works. However, this version is maintained and we will be happy to accept your bugreports [in our bug tracker](https://github.com/openvk/openvk/projects/1). You should also be able to submit them using [ticketing system](https://openvk.su/support?act=new) (you will need an OpenVK account for this).
|
||||
|
||||
## When's the release?
|
||||
|
||||
We will release OpenVK as soon as it's ready. As for now you can:
|
||||
We will release OpenVK as soon as it's ready. As for now, you can:
|
||||
* `git clone` this repo's master branch (use `git pull` to update)
|
||||
* Grab a prebuilt OpenVK distro from [GitHub artifacts](https://nightly.link/openvk/archive/workflows/nightly/master/OpenVK%20Archive.zip)
|
||||
|
||||
## Instances
|
||||
|
||||
* **[openvk.su](https://openvk.su/)**
|
||||
* **[openvk.uk](https://openvk.uk)** - official mirror of openvk.su (<https://t.me/openvk/1609>)
|
||||
* **[openvk.co](http://openvk.co)** - yet another official mirror of openvk.su without TLS (<https://t.me/openvk/1654>)
|
||||
* [social.fetbuk.ru](http://social.fetbuk.ru/)
|
||||
* [vepurovk.xyz](http://vepurovk.xyz/)
|
||||
A list of instances can be found in [our wiki of this repository](https://github.com/openvk/openvk/wiki/Instances).
|
||||
|
||||
## Can I create my own OpenVK instance?
|
||||
|
||||
Yes! And you're very welcome to.
|
||||
Yes! And you are very welcome to.
|
||||
|
||||
However, OVK makes use of Chandler Application Server. This software requires extensions, that may not be provided by your hosting provider (namely, sodium and yaml. these extensions are available on most of ISPManager hostings).
|
||||
|
||||
|
@ -34,12 +30,12 @@ If you want, you can add your instance to the list above so that people can regi
|
|||
|
||||
1. Install PHP 7.4, web-server, Composer, Node.js, Yarn and [Chandler](https://github.com/openvk/chandler)
|
||||
|
||||
* PHP 8.1 is supported, but it was not tested carefully, be aware of that.
|
||||
* PHP 8.1 is supported too, however it was not tested carefully, so be aware.
|
||||
|
||||
2. Install MySQL-compatible database.
|
||||
|
||||
* We recommend using Percona Server, but any MySQL-compatible server should work
|
||||
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ recommended.
|
||||
* We recommend using Percona Server, but any MySQL-compatible server should work too.
|
||||
* Server should be compatible with at least MySQL 5.6, MySQL 8.0+ is recommended.
|
||||
* Support for MySQL 4.1+ is WIP, replace `utf8mb4` and `utf8mb4_unicode_520_ci` with `utf8` and `utf8_unicode_ci` in SQLs.
|
||||
|
||||
3. Install [commitcaptcha](https://github.com/openvk/commitcaptcha) and OpenVK as Chandler extensions like this:
|
||||
|
@ -77,20 +73,20 @@ See `install/automated/docker/README.md` and `install/automated/kubernetes/READM
|
|||
|
||||
### If my website uses OpenVK, should I release it's sources?
|
||||
|
||||
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you're planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
|
||||
It depends. You can keep the sources to yourself if you do not plan to distribute your website binaries. If your website software must be distributed, it can stay non-OSS provided the OpenVK is not used as a primary application and is not modified. If you modified OpenVK for your needs or your work is based on it and you are planning to redistribute this, then you should license it under terms of any LGPL-compatible license (like OSL, GPL, LGPL etc).
|
||||
|
||||
## Where can I get assistance?
|
||||
|
||||
You may reach out to us via:
|
||||
|
||||
* [Bug-tracker](https://github.com/openvk/openvk/projects/1)
|
||||
* [Ticketing system](https://openvk.su/support?act=new)
|
||||
* Telegram chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
|
||||
* [Bug Tracker](https://github.com/openvk/openvk/projects/1)
|
||||
* [Ticketing System](https://openvk.su/support?act=new)
|
||||
* Telegram Chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu.
|
||||
* [Reddit](https://www.reddit.com/r/openvk/)
|
||||
* [Discussions](https://github.com/openvk/openvk/discussions)
|
||||
* Matrix chat: #openvk:matrix.org
|
||||
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)
|
||||
* Matrix Chat: #openvk:matrix.org
|
||||
|
||||
**Attention**: bug tracker, board, telegram and matrix chat are public places. And ticketing system is being served by volunteers. If you need to report something, that shouldn't be immediately disclosed to general public (for instance, vulnerability report), please use contact us directly at this email: **openvk [at] tutanota [dot] com**
|
||||
**Attention**: bug tracker, board, Telegram and Matrix chat are public places, ticketing system is being served by volunteers. If you need to report something that should not be immediately disclosed to general public (for instance, a vulnerability), please contact us directly via this email: **openvk [at] tutanota [dot] com**
|
||||
|
||||
<a href="https://codeberg.org/OpenVK/openvk">
|
||||
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">
|
||||
|
|
20
README_RU.md
|
@ -2,11 +2,11 @@
|
|||
|
||||
_[English](README.md)_
|
||||
|
||||
**OpenVK** - это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент представленный здесь исходный код проекта пока не является стабильным.
|
||||
**OpenVK** — это попытка создать простую CMS, которая ~~косплеит~~ имитирует старый ВКонтакте. На данный момент, представленный здесь исходный код проекта пока не является стабильным.
|
||||
|
||||
ВКонтакте принадлежит Павлу Дурову и VK Group.
|
||||
|
||||
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OVK).
|
||||
Честно говоря, мы даже не знаем, работает ли она вообще. Однако, эта версия поддерживается, и мы будем рады принять ваши сообщения об ошибках [в нашем баг-трекере](https://github.com/openvk/openvk/projects/1). Вы также можете отправлять их через [вкладку "Помощь"](https://openvk.su/support?act=new) (для этого вам понадобится учетная запись OpenVK).
|
||||
|
||||
## Когда выйдет релизная версия?
|
||||
|
||||
|
@ -16,19 +16,15 @@ _[English](README.md)_
|
|||
|
||||
## Инстанции
|
||||
|
||||
* **[openvk.su](https://openvk.su/)**
|
||||
* **[openvk.uk](https://openvk.uk)** - официальное зеркало openvk.su (<https://t.me/openvk/1609>)
|
||||
* **[openvk.co](http://openvk.co)** - ещё одно официальное зеркало openvk.su без TLS (<https://t.me/openvk/1654>)
|
||||
* [social.fetbuk.ru](http://social.fetbuk.ru/)
|
||||
* [vepurovk.xyz](http://vepurovk.xyz/)
|
||||
Список инстанций находится в [нашей вики этого репозитория](https://github.com/openvk/openvk/wiki/Instances-(RU)).
|
||||
|
||||
## Могу ли я создать свою собственную инстанцию OpenVK?
|
||||
|
||||
Да! И всегда пожалуйста.
|
||||
|
||||
Однако, OVK использует Chandler Application Server. Это программное обеспечение требует расширений, которые могут быть не предоставлены вашим хостинг-провайдером (а именно, sodium и yaml. эти расширения доступны на большинстве хостингов ISPManager).
|
||||
Однако, OpenVK использует Chandler Application Server. Это программное обеспечение требует расширений, которые могут быть не предоставлены вашим хостинг-провайдером (а именно, sodium и yaml. Эти расширения доступны на большинстве хостингов ISPManager).
|
||||
|
||||
Если вы хотите, вы можете добавить вашу инстанцию в список выше, чтобы люди могли зарегистрироваться там.
|
||||
Если хотите, вы можете добавить вашу инстанцию в список выше, чтобы люди могли зарегистрироваться там.
|
||||
|
||||
### Процедура установки
|
||||
|
||||
|
@ -38,7 +34,7 @@ _[English](README.md)_
|
|||
|
||||
2. Установите MySQL-совместимую базу данных.
|
||||
|
||||
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать
|
||||
* Мы рекомендуем использовать Persona Server, но любая MySQL-совместимая база данных должна работать.
|
||||
* Сервер должен поддерживать хотя бы MySQL 5.6, рекомендуется использовать MySQL 8.0+.
|
||||
* Поддержка для MySQL 4.1+ находится в процессе, а пока замените `utf8mb4` и `utf8mb4_unicode_520_ci` на `utf8` и `utf8_unicode_ci` в SQL-файлах, соответственно.
|
||||
|
||||
|
@ -87,10 +83,10 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions
|
|||
* [Помощь в OVK](https://openvk.su/support?act=new)
|
||||
* Telegram-чат: Перейдите на [наш канал](https://t.me/openvk) и откройте обсуждение в меню нашего канала.
|
||||
* [Reddit](https://www.reddit.com/r/openvk/)
|
||||
* [Обсуждения](https://github.com/openvk/openvk/discussions)
|
||||
* [GitHub Discussions](https://github.com/openvk/openvk/discussions)
|
||||
* Чат в Matrix: #ovk:matrix.org
|
||||
|
||||
**Внимание**: баг-трекер, форум, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**.
|
||||
**Внимание**: баг-трекер, форум, Telegram- и Matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собачка] tutanota [точка] com**.
|
||||
|
||||
<a href="https://codeberg.org/OpenVK/openvk">
|
||||
<img alt="Get it on Codeberg" src="https://codeberg.org/Codeberg/GetItOnCodeberg/media/branch/main/get-it-on-blue-on-white.png" height="60">
|
||||
|
|
|
@ -45,14 +45,12 @@ final class Account extends VKAPIRequestHandler
|
|||
{
|
||||
$this->requireUser();
|
||||
|
||||
$this->getUser()->setOnline(time());
|
||||
$this->getUser()->setClient_name($this->getPlatform());
|
||||
$this->getUser()->save();
|
||||
$this->getUser()->updOnline($this->getPlatform());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
function setOffline(): object
|
||||
function setOffline(): int
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
|
@ -80,6 +78,8 @@ final class Account extends VKAPIRequestHandler
|
|||
function saveProfileInfo(string $first_name = "", string $last_name = "", string $screen_name = "", int $sex = -1, int $relation = -1, string $bdate = "", int $bdate_visibility = -1, string $home_town = "", string $status = ""): object
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
$output = [
|
||||
|
|
|
@ -66,6 +66,7 @@ final class Friends extends VKAPIRequestHandler
|
|||
function add(string $user_id): int
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$users = new UsersRepo;
|
||||
$user = $users->get(intval($user_id));
|
||||
|
@ -96,6 +97,7 @@ final class Friends extends VKAPIRequestHandler
|
|||
function delete(string $user_id): int
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$users = new UsersRepo;
|
||||
|
||||
|
@ -152,10 +154,7 @@ final class Friends extends VKAPIRequestHandler
|
|||
$response = $followers;
|
||||
$usersApi = new Users($this->getUser());
|
||||
|
||||
if($extended == 1)
|
||||
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count);
|
||||
else
|
||||
$response = $usersApi->get(implode(',', $followers), "", 0, $count);
|
||||
$response = $usersApi->get(implode(',', $followers), $fields, 0, $count);
|
||||
|
||||
foreach($response as $user)
|
||||
$user->user_id = $user->id;
|
||||
|
|
|
@ -237,6 +237,7 @@ final class Groups extends VKAPIRequestHandler
|
|||
function join(int $group_id)
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$club = (new ClubsRepo)->get($group_id);
|
||||
|
||||
|
@ -251,6 +252,7 @@ final class Groups extends VKAPIRequestHandler
|
|||
function leave(int $group_id)
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$club = (new ClubsRepo)->get($group_id);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ final class Likes extends VKAPIRequestHandler
|
|||
function add(string $type, int $owner_id, int $item_id): object
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
switch($type) {
|
||||
case "post":
|
||||
|
@ -28,6 +29,7 @@ final class Likes extends VKAPIRequestHandler
|
|||
function delete(string $type, int $owner_id, int $item_id): object
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
switch($type) {
|
||||
case "post":
|
||||
|
|
|
@ -65,10 +65,16 @@ final class Messages extends VKAPIRequestHandler
|
|||
];
|
||||
}
|
||||
|
||||
function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1)
|
||||
function send(int $user_id = -1, int $peer_id = -1, string $domain = "", int $chat_id = -1, string $user_ids = "", string $message = "", int $sticker_id = -1, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
if($forGodSakePleaseDoNotReportAboutMyOnlineActivity == 0)
|
||||
{
|
||||
$this->getUser()->updOnline($this->getPlatform());
|
||||
}
|
||||
|
||||
if($chat_id !== -1)
|
||||
$this->fail(946, "Chats are not implemented");
|
||||
else if($sticker_id !== -1)
|
||||
|
@ -117,6 +123,7 @@ final class Messages extends VKAPIRequestHandler
|
|||
function delete(string $message_ids, int $spam = 0, int $delete_for_all = 0): object
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$msgs = new MSGRepo;
|
||||
$ids = preg_split("%, ?%", $message_ids);
|
||||
|
@ -136,6 +143,7 @@ final class Messages extends VKAPIRequestHandler
|
|||
function restore(int $message_id): int
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$msg = (new MSGRepo)->get($message_id);
|
||||
if(!$msg)
|
||||
|
|
|
@ -7,9 +7,14 @@ use openvk\VKAPI\Handlers\Wall;
|
|||
|
||||
final class Newsfeed extends VKAPIRequestHandler
|
||||
{
|
||||
function get(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0)
|
||||
function get(string $fields = "", int $start_from = 0, int $offset = 0, int $count = 30, int $extended = 0, int $forGodSakePleaseDoNotReportAboutMyOnlineActivity = 0)
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
if($forGodSakePleaseDoNotReportAboutMyOnlineActivity == 0)
|
||||
{
|
||||
$this->getUser()->updOnline($this->getPlatform());
|
||||
}
|
||||
|
||||
$id = $this->getUser()->getId();
|
||||
$subs = DatabaseConnection::i()
|
||||
|
|
|
@ -66,6 +66,7 @@ final class Polls extends VKAPIRequestHandler
|
|||
function addVote(int $poll_id, string $answers_ids)
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$poll = (new PollsRepo)->get($poll_id);
|
||||
|
||||
|
@ -87,6 +88,7 @@ final class Polls extends VKAPIRequestHandler
|
|||
function deleteVote(int $poll_id)
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$poll = (new PollsRepo)->get($poll_id);
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\VKAPI\Exceptions\APIErrorException;
|
||||
use openvk\Web\Models\Entities\IP;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\IPs;
|
||||
|
||||
abstract class VKAPIRequestHandler
|
||||
{
|
||||
|
@ -39,4 +41,19 @@ abstract class VKAPIRequestHandler
|
|||
if(!$this->userAuthorized())
|
||||
$this->fail(5, "User authorization failed: no access_token passed.");
|
||||
}
|
||||
|
||||
protected function willExecuteWriteAction(): void
|
||||
{
|
||||
$ip = (new IPs)->get(CONNECTING_IP);
|
||||
$res = $ip->rateLimit();
|
||||
|
||||
if(!($res === IP::RL_RESET || $res === IP::RL_CANEXEC)) {
|
||||
if($res === IP::RL_BANNED && OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["autoban"]) {
|
||||
$this->user->ban("User account has been suspended for breaking API terms of service", false);
|
||||
$this->fail(18, "User account has been suspended due to repeated violation of API rate limits.");
|
||||
}
|
||||
|
||||
$this->fail(29, "You have been rate limited.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
57
VKAPI/Handlers/Video.php
Executable file
|
@ -0,0 +1,57 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\VKAPI\Handlers;
|
||||
use openvk\Web\Models\Entities\User;
|
||||
use openvk\Web\Models\Repositories\Users as UsersRepo;
|
||||
use openvk\Web\Models\Entities\Club;
|
||||
use openvk\Web\Models\Repositories\Clubs as ClubsRepo;
|
||||
use openvk\Web\Models\Entities\Video as VideoEntity;
|
||||
use openvk\Web\Models\Repositories\Videos as VideosRepo;
|
||||
use openvk\Web\Models\Entities\Comment;
|
||||
use openvk\Web\Models\Repositories\Comments as CommentsRepo;
|
||||
|
||||
final class Video extends VKAPIRequestHandler
|
||||
{
|
||||
function get(int $owner_id, string $videos, int $offset = 0, int $count = 30, int $extended = 0): object
|
||||
{
|
||||
$this->requireUser();
|
||||
|
||||
if ($videos) {
|
||||
$vids = explode(',', $videos);
|
||||
|
||||
foreach($vids as $vid)
|
||||
{
|
||||
$id = explode("_", $vid);
|
||||
|
||||
$items = [];
|
||||
|
||||
$video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1]));
|
||||
if($video) {
|
||||
$items[] = $video->getApiStructure();
|
||||
}
|
||||
}
|
||||
|
||||
return (object) [
|
||||
"count" => count($items),
|
||||
"items" => $items
|
||||
];
|
||||
} else {
|
||||
if ($owner_id > 0)
|
||||
$user = (new UsersRepo)->get($owner_id);
|
||||
else
|
||||
$this->fail(1, "Not implemented");
|
||||
|
||||
$videos = (new VideosRepo)->getByUser($user, $offset + 1, $count);
|
||||
$videosCount = (new VideosRepo)->getUserVideosCount($user);
|
||||
|
||||
$items = [];
|
||||
foreach ($videos as $video) {
|
||||
$items[] = $video->getApiStructure();
|
||||
}
|
||||
|
||||
return (object) [
|
||||
"count" => $videosCount,
|
||||
"items" => $items
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
|
||||
$attachments[] = $this->getApiPoll($attachment, $this->getUser());
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
|
||||
$attachments[] = $attachment->getApiStructure();
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
|
||||
$repostAttachments = [];
|
||||
|
||||
|
@ -216,6 +218,8 @@ final class Wall extends VKAPIRequestHandler
|
|||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
} else if($attachment instanceof \openvk\Web\Models\Entities\Poll) {
|
||||
$attachments[] = $this->getApiPoll($attachment, $user);
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Video) {
|
||||
$attachments[] = $attachment->getApiStructure();
|
||||
} else if ($attachment instanceof \openvk\Web\Models\Entities\Post) {
|
||||
$repostAttachments = [];
|
||||
|
||||
|
@ -362,6 +366,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object
|
||||
{
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$owner_id = intval($owner_id);
|
||||
|
||||
|
@ -446,6 +451,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
|
||||
function repost(string $object, string $message = "") {
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$postArray;
|
||||
if(preg_match('/wall((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0)
|
||||
|
@ -489,6 +495,14 @@ final class Wall extends VKAPIRequestHandler
|
|||
$oid = $owner->getId();
|
||||
if($owner instanceof Club)
|
||||
$oid *= -1;
|
||||
|
||||
$attachments = [];
|
||||
|
||||
foreach($comment->getChildren() as $attachment) {
|
||||
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
|
||||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
$item = [
|
||||
"id" => $comment->getId(),
|
||||
|
@ -498,6 +512,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
"post_id" => $post->getVirtualId(),
|
||||
"owner_id" => $post->isPostedOnBehalfOfGroup() ? $post->getOwner()->getId() * -1 : $post->getOwner()->getId(),
|
||||
"parents_stack" => [],
|
||||
"attachments" => $attachments,
|
||||
"thread" => [
|
||||
"count" => 0,
|
||||
"items" => [],
|
||||
|
@ -518,6 +533,9 @@ final class Wall extends VKAPIRequestHandler
|
|||
$items[] = $item;
|
||||
if($extended == true)
|
||||
$profiles[] = $comment->getOwner()->getId();
|
||||
|
||||
$attachments = null;
|
||||
// Reset $attachments to not duplicate prikols
|
||||
}
|
||||
|
||||
$response = [
|
||||
|
@ -544,6 +562,14 @@ final class Wall extends VKAPIRequestHandler
|
|||
|
||||
$profiles = [];
|
||||
|
||||
$attachments = [];
|
||||
|
||||
foreach($comment->getChildren() as $attachment) {
|
||||
if($attachment instanceof \openvk\Web\Models\Entities\Photo) {
|
||||
$attachments[] = $this->getApiPhoto($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
$item = [
|
||||
"id" => $comment->getId(),
|
||||
"from_id" => $comment->getOwner()->getId(),
|
||||
|
@ -552,6 +578,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
"post_id" => $comment->getTarget()->getVirtualId(),
|
||||
"owner_id" => $comment->getTarget()->isPostedOnBehalfOfGroup() ? $comment->getTarget()->getOwner()->getId() * -1 : $comment->getTarget()->getOwner()->getId(),
|
||||
"parents_stack" => [],
|
||||
"attachments" => $attachments,
|
||||
"likes" => [
|
||||
"can_like" => 1,
|
||||
"count" => $comment->getLikesCount(),
|
||||
|
@ -582,10 +609,15 @@ final class Wall extends VKAPIRequestHandler
|
|||
$response['profiles'] = (!empty($profiles) ? (new Users)->get(implode(',', $profiles), $fields) : []);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function createComment(int $owner_id, int $post_id, string $message, int $from_group = 0) {
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$post = (new PostsRepo)->getPostById($owner_id, $post_id);
|
||||
if(!$post || $post->isDeleted()) $this->fail(100, "One of the parameters specified was missing or invalid");
|
||||
|
||||
|
@ -621,6 +653,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
|
||||
function deleteComment(int $comment_id) {
|
||||
$this->requireUser();
|
||||
$this->willExecuteWriteAction();
|
||||
|
||||
$comment = (new CommentsRepo)->get($comment_id);
|
||||
if(!$comment) $this->fail(100, "One of the parameters specified was missing or invalid");;
|
||||
|
@ -640,7 +673,7 @@ final class Wall extends VKAPIRequestHandler
|
|||
"date" => $attachment->getPublicationTime()->timestamp(),
|
||||
"id" => $attachment->getVirtualId(),
|
||||
"owner_id" => $attachment->getOwner()->getId(),
|
||||
"sizes" => array_values($attachment->getVkApiSizes()),
|
||||
"sizes" => !is_null($attachment->getVkApiSizes()) ? array_values($attachment->getVkApiSizes()) : NULL,
|
||||
"text" => "",
|
||||
"has_tags" => false
|
||||
]
|
||||
|
|
|
@ -283,6 +283,14 @@ class Photo extends Media
|
|||
return [$x, $y];
|
||||
}
|
||||
|
||||
function getPageURL(): string
|
||||
{
|
||||
if($this->isAnonymous())
|
||||
return "/photos/" . base_convert((string) $this->getId(), 10, 32);
|
||||
|
||||
return "/photo" . $this->getPrettyId();
|
||||
}
|
||||
|
||||
function getAlbum(): ?Album
|
||||
{
|
||||
return (new Albums)->getAlbumByPhotoId($this);
|
||||
|
|
|
@ -119,7 +119,7 @@ class Post extends Postable
|
|||
$platform = $this->getRecord()->api_source_name;
|
||||
if($forAPI) {
|
||||
switch ($platform) {
|
||||
case 'openvk_android':
|
||||
case 'openvk_refresh_android':
|
||||
case 'openvk_legacy_android':
|
||||
return 'android';
|
||||
break;
|
||||
|
|
|
@ -36,9 +36,9 @@ trait TRichText
|
|||
"%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%",
|
||||
(function (array $matches): string {
|
||||
$href = str_replace("#", "#", $matches[1]);
|
||||
$href = rawurlencode(str_replace(";", ";", $matches[1]));
|
||||
$href = rawurlencode(str_replace(";", ";", $href));
|
||||
$link = str_replace("#", "#", $matches[3]);
|
||||
$link = str_replace(";", ";", $matches[3]);
|
||||
$link = str_replace(";", ";", $link);
|
||||
$rel = $this->isAd() ? "sponsored" : "ugc";
|
||||
|
||||
return "<a href='/away.php?to=$href' rel='$rel' target='_blank'>$link</a>" . htmlentities($matches[4]);
|
||||
|
@ -49,7 +49,7 @@ trait TRichText
|
|||
|
||||
private function removeZalgo(string $text): string
|
||||
{
|
||||
return preg_replace("%[\x{0300}-\x{036F}]{3,}%Xu", "<EFBFBD>", $text);
|
||||
return preg_replace("%\p{M}{3,}%Xu", "", $text);
|
||||
}
|
||||
|
||||
function resolveMentions(array $skipUsers = []): \Traversable
|
||||
|
|
|
@ -756,7 +756,7 @@ class User extends RowModel
|
|||
$platform = $this->getRecord()->client_name;
|
||||
if($forAPI) {
|
||||
switch ($platform) {
|
||||
case 'openvk_android':
|
||||
case 'openvk_refresh_android':
|
||||
case 'openvk_legacy_android':
|
||||
return 'android';
|
||||
break;
|
||||
|
@ -1009,6 +1009,15 @@ class User extends RowModel
|
|||
return true;
|
||||
}
|
||||
|
||||
function updOnline(string $platform): bool
|
||||
{
|
||||
$this->setOnline(time());
|
||||
$this->setClient_name($platform);
|
||||
$this->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function changeEmail(string $email): void
|
||||
{
|
||||
DatabaseConnection::i()->getContext()->table("ChandlerUsers")
|
||||
|
|
|
@ -13,7 +13,7 @@ class Video extends Media
|
|||
const TYPE_EMBED = 1;
|
||||
|
||||
protected $tableName = "videos";
|
||||
protected $fileExtension = "ogv";
|
||||
protected $fileExtension = "mp4";
|
||||
|
||||
protected $processingPlaceholder = "video/rendering";
|
||||
|
||||
|
@ -30,7 +30,7 @@ class Video extends Media
|
|||
throw new \DomainException("$filename does not contain any video streams");
|
||||
|
||||
$durations = [];
|
||||
preg_match('%duration=([0-9\.]++)%', $streams, $durations);
|
||||
preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
|
||||
if(sizeof($durations[1]) === 0)
|
||||
throw new \DomainException("$filename does not contain any meaningful video streams");
|
||||
|
||||
|
@ -104,7 +104,7 @@ class Video extends Media
|
|||
if(!$this->isProcessed())
|
||||
return "/assets/packages/static/openvk/video/rendering.apng";
|
||||
|
||||
return preg_replace("%\.[A-z]++$%", ".gif", $this->getURL());
|
||||
return preg_replace("%\.[A-z0-9]++$%", ".gif", $this->getURL());
|
||||
} else {
|
||||
return $this->getVideoDriver()->getThumbnailURL();
|
||||
}
|
||||
|
@ -114,6 +114,56 @@ class Video extends Media
|
|||
{
|
||||
return $this->getRecord()->owner;
|
||||
}
|
||||
|
||||
function getApiStructure(): object
|
||||
{
|
||||
return (object)[
|
||||
"type" => "video",
|
||||
"video" => [
|
||||
"can_comment" => 1,
|
||||
"can_like" => 0, // we don't h-have wikes in videos
|
||||
"can_repost" => 0,
|
||||
"can_subscribe" => 1,
|
||||
"can_add_to_faves" => 0,
|
||||
"can_add" => 0,
|
||||
"comments" => $this->getCommentsCount(),
|
||||
"date" => $this->getPublicationTime()->timestamp(),
|
||||
"description" => $this->getDescription(),
|
||||
"duration" => 0, // я хуй знает как получить длину видео
|
||||
"image" => [
|
||||
[
|
||||
"url" => $this->getThumbnailURL(),
|
||||
"width" => 320,
|
||||
"height" => 240,
|
||||
"with_padding" => 1
|
||||
]
|
||||
],
|
||||
"width" => 640,
|
||||
"height" => 480,
|
||||
"id" => $this->getVirtualId(),
|
||||
"owner_id" => $this->getOwner()->getId(),
|
||||
"user_id" => $this->getOwner()->getId(),
|
||||
"title" => $this->getName(),
|
||||
"is_favorite" => false,
|
||||
"player" => $this->getURL(),
|
||||
"files" => [
|
||||
"mp4_480" => $this->getURL()
|
||||
],
|
||||
"added" => 0,
|
||||
"repeat" => 0,
|
||||
"type" => "video",
|
||||
"views" => 0,
|
||||
"likes" => [
|
||||
"count" => 0,
|
||||
"user_likes" => 0
|
||||
],
|
||||
"reposts" => [
|
||||
"count" => 0,
|
||||
"user_reposted" => 0
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
function setLink(string $link): string
|
||||
{
|
||||
|
|
|
@ -14,5 +14,5 @@ abstract class VideoDriver
|
|||
|
||||
abstract function getURL(): string;
|
||||
|
||||
abstract function getEmbed(): string;
|
||||
abstract function getEmbed(string $w = "600", string $h = "340"): string;
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ final class YouTubeVideoDriver extends VideoDriver
|
|||
return "https://youtu.be/$this->id";
|
||||
}
|
||||
|
||||
function getEmbed(): string
|
||||
function getEmbed(string $w = "600", string $h = "340"): string
|
||||
{
|
||||
return <<<CODE
|
||||
<iframe
|
||||
width="600"
|
||||
height="340"
|
||||
width="$w"
|
||||
height="$h"
|
||||
src="https://www.youtube-nocookie.com/embed/$this->id"
|
||||
frameborder="0"
|
||||
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||
|
|
|
@ -13,8 +13,8 @@ Move-Item $file $temp
|
|||
|
||||
# video stub logic was implicitly deprecated, so we start processing at once
|
||||
ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif"
|
||||
ffmpeg -i $temp -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
|
||||
ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y $temp2
|
||||
|
||||
Move-Item $temp2 "$dir$hashT/$hash.ogv"
|
||||
Move-Item $temp2 "$dir$hashT/$hash.mp4"
|
||||
Remove-Item $temp
|
||||
Remove-Item $temp2
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
tmpfile="$RANDOM-$(date +%s%N)"
|
||||
|
||||
cp $2 "/tmp/vid_$tmpfile.bin"
|
||||
cp ../files/video/rendering.apng $3${4:0:2}/$4.gif
|
||||
cp ../files/video/rendering.ogv $3/${4:0:2}/$4.ogv
|
||||
|
||||
nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif
|
||||
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y "/tmp/ffmOi$tmpfile.ogv"
|
||||
nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2,setsar=1" -y "/tmp/ffmOi$tmpfile.mp4"
|
||||
|
||||
rm -rf $3${4:0:2}/$4.ogv
|
||||
mv "/tmp/ffmOi$tmpfile.ogv" $3${4:0:2}/$4.ogv
|
||||
rm -rf $3${4:0:2}/$4.mp4
|
||||
mv "/tmp/ffmOi$tmpfile.mp4" $3${4:0:2}/$4.mp4
|
||||
|
||||
rm -f "/tmp/ffmOi$tmpfile.ogv"
|
||||
rm -f "/tmp/ffmOi$tmpfile.mp4"
|
||||
rm -f "/tmp/vid_$tmpfile.bin"
|
||||
|
|
|
@ -80,7 +80,11 @@ final class AuthPresenter extends OpenVKPresenter
|
|||
|
||||
if(!Validator::i()->emailValid($this->postParam("email")))
|
||||
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
|
||||
|
||||
|
||||
if(OPENVK_ROOT_CONF['openvk']['preferences']['security']['forceStrongPassword'])
|
||||
if(!Validator::i()->passwordStrong($this->postParam("password")))
|
||||
$this->flashFail("err", tr("error"), tr("error_weak_password"));
|
||||
|
||||
if (strtotime($this->postParam("birthday")) > time())
|
||||
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
|
||||
|
||||
|
|
|
@ -57,6 +57,11 @@ final class MessengerPresenter extends OpenVKPresenter
|
|||
$correspondent = $this->getCorrespondent($sel);
|
||||
if(!$correspondent)
|
||||
$this->notFound();
|
||||
|
||||
if(!$this->user->identity->getPrivacyPermission('messages.write', $correspondent))
|
||||
{
|
||||
$this->flash("err", tr("warning"), tr("user_may_not_reply"));
|
||||
}
|
||||
|
||||
$this->template->selId = $sel;
|
||||
$this->template->correspondent = $correspondent;
|
||||
|
|
|
@ -204,6 +204,9 @@ final class VKAPIPresenter extends OpenVKPresenter
|
|||
}
|
||||
}
|
||||
|
||||
if(!is_null($identity) && $identity->isBanned())
|
||||
$this->fail(18, "User account is deactivated", $object, $method);
|
||||
|
||||
$object = ucfirst(strtolower($object));
|
||||
$handlerClass = "openvk\\VKAPI\\Handlers\\$object";
|
||||
if(!class_exists($handlerClass))
|
||||
|
|
|
@ -218,8 +218,8 @@
|
|||
<input id="password" type="password" name="password" required />
|
||||
<input type="hidden" name="jReturnTo" value="{$_SERVER['REQUEST_URI']}" />
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
<input type="submit" value="{_log_in}" class="button" style="display: inline-block;" />
|
||||
<a href="/reg" class="button" style="display: inline-block;">{_registration}</a><br><br>
|
||||
<input type="submit" value="{_log_in}" class="button" style="display: inline-block; font-family: Tahoma" />
|
||||
<a href="/reg"><input type="button" value="{_registration}" class="button" style="font-family: Tahoma" /></a><br><br>
|
||||
<a href="/restore">{_forgot_password}</a>
|
||||
</form>
|
||||
{/ifset}
|
||||
|
@ -294,6 +294,7 @@
|
|||
{script "js/messagebox.js"}
|
||||
{script "js/notifications.js"}
|
||||
{script "js/scroll.js"}
|
||||
{script "js/player.js"}
|
||||
{script "js/al_wall.js"}
|
||||
{script "js/al_api.js"}
|
||||
{script "js/al_mentions.js"}
|
||||
|
@ -310,6 +311,8 @@
|
|||
</script>
|
||||
{/if}
|
||||
|
||||
<script>bsdnHydrate();</script>
|
||||
|
||||
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['enable']" async defer data-domain="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['domain']}" src="{php echo OPENVK_ROOT_CONF['openvk']['telemetry']['plausible']['server']}js/plausible.js"></script>
|
||||
|
||||
<script n:if="OPENVK_ROOT_CONF['openvk']['telemetry']['piwik']['enable']">
|
||||
|
|
|
@ -411,7 +411,7 @@
|
|||
<tr>
|
||||
<td class="e">
|
||||
Vladimir Barinov (veselcraft) and Konstantin Kichulkin (kosfurler)<br/>
|
||||
OpenVK is a free open-source software that "cosplays" (or imitates) older versions of russian website VKontakte. VKontakte belongs to Pavel Durov and VK Group.
|
||||
OpenVK is a free open source software that "cosplays" (or imitates) older versions of a Russian social network called VKontakte. VKontakte belongs to Pavel Durov and VK Group.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -430,7 +430,7 @@
|
|||
Native name
|
||||
</td>
|
||||
<td>
|
||||
Author
|
||||
Author(s)
|
||||
</td>
|
||||
</tr>
|
||||
{foreach $languages as $language}
|
||||
|
|
|
@ -71,7 +71,10 @@
|
|||
<span class="nobold">{_avatar}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="file" name="ava" accept="image/*" />
|
||||
<label class="button" style="">{_browse}
|
||||
<input type="file" id="ava" name="ava" style="display: none;" onchange="filename.innerHTML=ava.files[0].name" />
|
||||
</label>
|
||||
<div id="filename" style="margin-top: 10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -37,9 +37,12 @@
|
|||
<form method="POST" enctype="multipart/form-data">
|
||||
<div id="backdropEditor">
|
||||
<div id="backdropFilePicker">
|
||||
<input type="file" accept="image/*" name="backdrop1" />
|
||||
<div id="spacer"></div>
|
||||
<input type="file" accept="image/*" name="backdrop2" />
|
||||
<label class="button" style="">{_browse}
|
||||
<input type="file" accept="image/*" name="backdrop1" style="display: none;">
|
||||
</label>
|
||||
<div id="spacer" style="width: 366px;"></div>
|
||||
<label class="button" style="">{_browse}<input type="file" accept="image/*" name="backdrop2" style="display: none;"></label>
|
||||
<div id="spacer" style="width: 366px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,7 +21,12 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_photo}:</span></td>
|
||||
<td><input type="file" name="blob" accept="image/*" /></td>
|
||||
<td>
|
||||
<label class="button" style="">{_browse}
|
||||
<input type="file" id="blob" name="blob" style="display: none;" onchange="filename.innerHTML=blob.files[0].name" />
|
||||
</label>
|
||||
<div id="filename" style="margin-top: 10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"></td>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<b>Подтверждение номера телефона</b><br/>
|
||||
Введите код для подтверждения смены номера: <a href="/edit/verify_phone">ввести код</a>.
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tabs">
|
||||
<div n:attr="id => ($isMain ? 'activetabs' : 'ki')" class="tab">
|
||||
<a n:attr="id => ($isMain ? 'act_tab_a' : 'ki')" href="/edit">{_main}</a>
|
||||
|
@ -38,10 +38,10 @@
|
|||
<a n:attr="id => ($isBackDrop ? 'act_tab_a' : 'ki')" href="/edit?act=backdrop">{_backdrop_short}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container_gray">
|
||||
{if $isMain}
|
||||
|
||||
|
||||
<h4>{_main_information}</h4>
|
||||
<form action="/edit?act=main" method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
|
@ -133,7 +133,7 @@
|
|||
<option value="7" {if $user->getPoliticalViews() == 7}selected{/if}>{_politViews_7}</option>
|
||||
<option value="8" {if $user->getPoliticalViews() == 8}selected{/if}>{_politViews_8}</option>
|
||||
<option value="9" {if $user->getPoliticalViews() == 9}selected{/if}>{_politViews_9}</option>
|
||||
|
||||
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -162,7 +162,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -172,9 +172,9 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
{elseif $isContacts}
|
||||
|
||||
|
||||
<h4>{_contact_information}</h4>
|
||||
<form action="/edit?act=contacts" method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
|
@ -221,7 +221,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -231,9 +231,9 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
{elseif $isInterests}
|
||||
|
||||
|
||||
<h4>{_personal_information}</h4>
|
||||
<form action="/edit?act=interests" method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
|
@ -296,7 +296,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -306,9 +306,9 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
{elseif $isAvatar}
|
||||
|
||||
|
||||
<h4>{_profile_picture}</h4>
|
||||
<form action="/al_avatars" method="POST" enctype="multipart/form-data">
|
||||
<table cellspacing="7" cellpadding="0" width="60%" border="0" align="center">
|
||||
|
@ -318,12 +318,15 @@
|
|||
<span class="nobold">{_picture}: </span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="file" name="blob" accept="image/*" />
|
||||
<label class="button" style="">{_browse}
|
||||
<input type="file" id="blob" name="blob" style="display: none;" onchange="filename.innerHTML=blob.files[0].name" />
|
||||
</label>
|
||||
<div id="filename" style="margin-top: 10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<input type="hidden" name="hash" value="{$csrfToken}" />
|
||||
|
@ -333,7 +336,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
{elseif $isBackDrop}
|
||||
|
||||
<h4>{_backdrop}</h4>
|
||||
|
@ -341,9 +344,10 @@
|
|||
<form method="POST" enctype="multipart/form-data">
|
||||
<div id="backdropEditor">
|
||||
<div id="backdropFilePicker">
|
||||
<input type="file" accept="image/*" name="backdrop1" />
|
||||
<div id="spacer"></div>
|
||||
<input type="file" accept="image/*" name="backdrop2" />
|
||||
<label class="button" style="">Обзор<input type="file" accept="image/*" name="backdrop1" style="display: none;"></label>
|
||||
<div id="spacer" style="width: 366px;"></div>
|
||||
<label class="button" style="">Обзор<input type="file" accept="image/*" name="backdrop2" style="display: none;"></label>
|
||||
<div id="spacer" style="width: 366px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -366,5 +370,5 @@
|
|||
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
{/block}
|
||||
|
|
|
@ -25,7 +25,12 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_video}:</span></td>
|
||||
<td><input type="file" name="blob" accept="video/*" /></td>
|
||||
<td>
|
||||
<label class="button" style="">{_browse}
|
||||
<input type="file" id="blob" name="blob" style="display: none;" onchange="filename.innerHTML=blob.files[0].name" accept="video/*" />
|
||||
</label>
|
||||
<div id="filename" style="margin-top: 10px;"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="120" valign="top"><span class="nobold">{_video_link_to_yt}:</span></td>
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
{block content}
|
||||
<center style="margin-bottom: 8pt;">
|
||||
{if $video->getType() === 0}
|
||||
<video width="610" src="{$video->getURL()}" controls></video>
|
||||
<div class="bsdn" data-name="{$video->getName()}" data-author="{$user->getCanonicalName()}">
|
||||
<video width="610" src="{$video->getURL()}"></video>
|
||||
</div>
|
||||
{else}
|
||||
{var $driver = $video->getVideoDriver()}
|
||||
{if !$driver}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
{if $theme !== NULL}
|
||||
{if $theme->inheritDefault()}
|
||||
{css "css/bsdn.css"}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
@ -20,6 +21,7 @@
|
|||
<link rel="stylesheet" href="/themepack/{$theme->getId()}/{$theme->getVersion()}/resource/xmas.css" />
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/bsdn.css"}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/notifications.css"}
|
||||
|
@ -41,6 +43,7 @@
|
|||
{css "css/microblog.css"}
|
||||
{/if}
|
||||
{else}
|
||||
{css "css/bsdn.css"}
|
||||
{css "css/style.css"}
|
||||
{css "css/dialog.css"}
|
||||
{css "css/nsfw-posts.css"}
|
||||
|
@ -49,4 +52,4 @@
|
|||
{if $isXmas}
|
||||
{css "css/xmas.css"}
|
||||
{/if}
|
||||
{/ifset}
|
||||
{/ifset}
|
||||
|
|
|
@ -10,7 +10,23 @@
|
|||
</a>
|
||||
{/if}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Video}
|
||||
<video class="media" src="{$attachment->getURL()}" controls="controls"></video>
|
||||
{if $attachment->getType() === 0}
|
||||
<div class="bsdn media" data-name="{$attachment->getName()}" data-author="{$attachment->getOwner()->getCanonicalName()}">
|
||||
<video class="media" src="{$attachment->getURL()}"></video>
|
||||
</div>
|
||||
{else}
|
||||
{var $driver = $attachment->getVideoDriver()}
|
||||
{if !$driver}
|
||||
<span style="color:red;">{_version_incompatibility}</span>
|
||||
{else}
|
||||
{$driver->getEmbed("100%")|noescape}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="video-wowzer">
|
||||
<img src="/assets/packages/static/openvk/img/videoico.png" />
|
||||
<a href="/video{$attachment->getPrettyId()}">{$attachment->getName()}</a>
|
||||
</div>
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Poll}
|
||||
{presenter "openvk!Poll->view", $attachment->getId()}
|
||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Post}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{var $user = $notification->getModel(0)}
|
||||
{var $post = $notification->getModel(1)}
|
||||
|
||||
{_nt_you_were_mentioned_u} <a href="{$user->getURL()}"><b>{$user->getCanonicalName()}</b></a> {$notification->getDateTime()} <a href="/photo{$post->getURL()}"><b>{_nt_mention_in_photo}</b></a>: "{$notification->getData()}"
|
||||
{_nt_you_were_mentioned_u} <a href="{$user->getURL()}"><b>{$user->getCanonicalName()}</b></a> {$notification->getDateTime()} <a href="/photo{$post->getPageURL()}"><b>{_nt_mention_in_photo}</b></a>: "{$notification->getData()}"
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
{else}
|
||||
{var $deac = "post_deact_silent"}
|
||||
{/if}
|
||||
{var $compact = isset($compact) ? true : false}
|
||||
{var $club = isset($club) ? $club}
|
||||
|
||||
{var $commentTextAreaId = $post === NULL ? rand(1,300) : $post->getId()}
|
||||
|
||||
|
@ -16,7 +18,7 @@
|
|||
<tr>
|
||||
<td width="54" valign="top">
|
||||
<a href="{$author->getURL()}">
|
||||
<img src="{$author->getAvatarURL('miniscule')}" width="{ifset $compact}25{else}50{/ifset}" {ifset $compact}class="cCompactAvatars"{/ifset} />
|
||||
<img src="{$author->getAvatarURL('miniscule')}" width="{if $compact}25{else}50{/if}" {if $compact}class="cCompactAvatars"{/if} />
|
||||
<span n:if="!$post->isPostedOnBehalfOfGroup() && !$compact && $author->isOnline()" class="post-online">{_online}</span>
|
||||
</a>
|
||||
</td>
|
||||
|
@ -40,18 +42,18 @@
|
|||
</a>
|
||||
{/if}
|
||||
|
||||
{ifset $compact}
|
||||
{if $compact}
|
||||
<br>
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">
|
||||
{$post->getPublicationTime()}
|
||||
</a>
|
||||
{/ifset}
|
||||
{/if}
|
||||
|
||||
<span n:if="$post->isPinned()" class="nobold">{_pinned}</span>
|
||||
|
||||
<a n:if="$post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && !isset($compact)" class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
|
||||
<a n:if="$post->canBeDeletedBy($thisUser) && !($forceNoDeleteLink ?? false) && $compact == false" class="delete" href="/wall{$post->getPrettyId()}/delete"></a>
|
||||
|
||||
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false) && !isset($compact)}
|
||||
{if $post->canBePinnedBy($thisUser) && !($forceNoPinLink ?? false) && $compact == false}
|
||||
{if $post->isPinned()}
|
||||
<a class="pin" href="/wall{$post->getPrettyId()}/pin?act=unpin&hash={rawurlencode($csrfToken)}"></a>
|
||||
{else}
|
||||
|
@ -94,7 +96,7 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-menu" n:if="!isset($compact)">
|
||||
<div class="post-menu" n:if="$compact == false">
|
||||
<a href="/wall{$post->getPrettyId()}" class="date">{$post->getPublicationTime()}</a>
|
||||
<a n:if="!empty($platform)" class="client_app" data-app-tag="{$platform}" data-app-name="{$platformDetails['name']}" data-app-url="{$platformDetails['url']}" data-app-img="{$platformDetails['img']}">
|
||||
<img src="/assets/packages/static/openvk/img/app_icons_mini/{$post->getPlatform(this)}.svg">
|
||||
|
|
|
@ -22,5 +22,9 @@ class Validator
|
|||
return (bool) preg_match("/^(?:t.me\/|@)?([a-zA-Z0-9_]{0,32})$/", $telegram);
|
||||
}
|
||||
|
||||
function passwordStrong(string $password): bool{
|
||||
return (bool) preg_match("/^(?=.*[A-Z])(?=.*[0-9])(?=.*[a-z]).{8,}$/", $password);
|
||||
}
|
||||
|
||||
use TSimpleSingleton;
|
||||
}
|
||||
|
|
220
Web/static/css/bsdn.css
Normal file
|
@ -0,0 +1,220 @@
|
|||
.bsdn-player {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
font-family: Tahoma, sans-serif;
|
||||
user-select: none;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.bsdn-player > .bsdn_controls, .bsdn-player > .bsdn_teaserWrap, .bsdn-player > .bsdn_contextMenu {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bsdn_controls {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
background-color: hsl(0deg 0% 0% / 59%);
|
||||
border-top: 1px solid hsl(0deg 0% 100% / 70%);
|
||||
padding: 7px 12px;
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
gap: 6px;
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.bsdn-player.bsdn-dirty > .bsdn_controls {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bsdn_video {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bsdn_terebilkaUpperWrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bsdn_terebilkaWrap {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bsdn_logo {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
p.bsdn_timeWrap {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
font-size: 7pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bsdn_terebilkaLowerWrap, .bsdn_soundControlSubWrap {
|
||||
position: relative;
|
||||
height: 6px;
|
||||
border-top: 1px solid white;
|
||||
}
|
||||
|
||||
.bsdn_terebilkaBrick, .bsdn_soundControlBrick {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 15px;
|
||||
background-color: #fff;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bsdn_soundIcon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bsdn_soundControl {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.bsdn_soundControlBrick {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.bsdn_soundControlPadding {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
button.bsdn_playButton {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
padding: 0 8px;
|
||||
padding-left: 0;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bsdn_fullScreenButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bsdn_fullScreenButton > img:hover {
|
||||
background: url("/assets/packages/static/openvk/img/bsdn/fullscreen_hover.gif");
|
||||
object-fit: none;
|
||||
object-position: -64px 0;
|
||||
}
|
||||
|
||||
.bsdn_teaserWrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: hsl(0deg 0% 0% / 10%);
|
||||
}
|
||||
|
||||
.bsdn_teaser {
|
||||
display: flex;
|
||||
padding: 10px 20px;
|
||||
width: 266px;
|
||||
background-color: hsl(0deg 0% 14.17% / 74.12%);
|
||||
border-radius: 10px;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bsdn_teaserTitleBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.bsdn_teaserButton {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
time.bsdn_timeFull {
|
||||
margin-left: 10px;
|
||||
color: #b1b1b1;
|
||||
}
|
||||
|
||||
.bsdn-player._bsdn_playing .bsdn_controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: 1s opacity ease-in-out;
|
||||
transition-delay: 2s;
|
||||
}
|
||||
|
||||
.bsdn-player._bsdn_playing:hover .bsdn_controls {
|
||||
opacity: 1;
|
||||
pointer-events: unset;
|
||||
transition: .2s opacity ease-in-out;
|
||||
transition-delay: 0;
|
||||
}
|
||||
|
||||
|
||||
.bsdn_video > video {
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.bsdn_contextMenu {
|
||||
z-index: 3;
|
||||
background-color: #f4f4f4;
|
||||
padding: 6px;
|
||||
border: 1px solid #908f90;
|
||||
width: 232px;
|
||||
height: 169px;
|
||||
font-size: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bsdn_contextMenuElement {
|
||||
display: block;
|
||||
color: #666294;
|
||||
cursor: pointer;
|
||||
padding: 3px 0 3px 20px;
|
||||
}
|
||||
|
||||
.bsdn_contextMenu hr {
|
||||
margin: 5px 0;
|
||||
border-color: #878f8e;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
|
||||
.bsdn_contextMenuElement:hover, .bsdn_contextMenuElement:active {
|
||||
color: #fff;
|
||||
}
|
||||
.bsdn_contextMenuElement:hover {
|
||||
background-color: #9797c8;
|
||||
}
|
||||
|
||||
.bsdn_contextMenuElement:active {
|
||||
background-color: #5a5a8f;
|
||||
}
|
||||
|
||||
|
||||
.bsdn_teaserWrap span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bsdn_teaserButton > img {
|
||||
max-height: 50px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.bsdn_fullScreenButton > img, .bsdn_soundIcon {
|
||||
vertical-align: middle;
|
||||
}
|
|
@ -231,6 +231,7 @@ h1 {
|
|||
position: absolute;
|
||||
top: 140px;
|
||||
padding: 0 19px;
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
#backdropFilePicker > input {
|
||||
|
@ -861,6 +862,8 @@ span {
|
|||
|
||||
.content_list .cl_element {
|
||||
width: 33%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content_list.long .cl_element {
|
||||
|
@ -881,6 +884,7 @@ span {
|
|||
|
||||
.content_list .cl_element .cl_name .cl_lname {
|
||||
font-size: 7pt;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ava {
|
||||
|
@ -2381,7 +2385,7 @@ a.poll-retract-vote {
|
|||
}
|
||||
|
||||
.regform-left{
|
||||
text-align: right;
|
||||
text-align: right;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
|
@ -2403,5 +2407,23 @@ a.poll-retract-vote {
|
|||
}
|
||||
|
||||
.tour div {
|
||||
font-size: 11px; color:#000;
|
||||
font-size: 11px; color:#000;
|
||||
}
|
||||
|
||||
.video-wowzer > img {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.video-wowzer {
|
||||
font-weight: bolder;
|
||||
font-size: 12px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.video-wowzer a::before {
|
||||
content: "b";
|
||||
color: transparent;
|
||||
width: 12px;
|
||||
background-image: url(/assets/packages/static/openvk/img/videoico.png);
|
||||
display: none;
|
||||
}
|
||||
|
|
BIN
Web/static/img/app_icons/openvk_refresh.png
Normal file
After Width: | Height: | Size: 9 KiB |
BIN
Web/static/img/bsdn/fullscreen.gif
Normal file
After Width: | Height: | Size: 333 B |
BIN
Web/static/img/bsdn/fullscreen_hover.gif
Normal file
After Width: | Height: | Size: 307 B |
BIN
Web/static/img/bsdn/pause.gif
Normal file
After Width: | Height: | Size: 248 B |
BIN
Web/static/img/bsdn/play.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
Web/static/img/bsdn/speaker.gif
Normal file
After Width: | Height: | Size: 240 B |
BIN
Web/static/img/bsdn/speaker_muted.gif
Normal file
After Width: | Height: | Size: 88 B |
BIN
Web/static/img/videoico.png
Normal file
After Width: | Height: | Size: 130 B |
|
@ -428,6 +428,18 @@ function showIncreaseRatingDialog(coinsCount, userUrl, hash) {
|
|||
};
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
$(document).on("scroll", () => {
|
||||
if($(document).scrollTop() > $(".sidebar").height() + 50) {
|
||||
$(".floating_sidebar")[0].classList.add("show");
|
||||
|
|
336
Web/static/js/player.js
Normal file
|
@ -0,0 +1,336 @@
|
|||
function _bsdnUnwrapBitMask(number) {
|
||||
return number.toString(2).split("").reverse().map(x => x === "1");
|
||||
}
|
||||
|
||||
function _bsdnToHumanTime(time) {
|
||||
time = Math.ceil(time);
|
||||
let mins = Math.floor(time / 60);
|
||||
let secs = (time - (mins * 60));
|
||||
|
||||
if(secs < 10)
|
||||
secs = "0" + secs;
|
||||
if(mins < 10)
|
||||
mins = "0" + mins;
|
||||
|
||||
return mins + ":" + secs;
|
||||
}
|
||||
|
||||
function _bsdnTpl(name, author) {
|
||||
name = escapeHtml(name);
|
||||
author = escapeHtml(author);
|
||||
|
||||
return `
|
||||
<div class="bsdn_contextMenu" style="display: none;">
|
||||
<span class="bsdn_contextMenuElement bsdn_copyVideoUrl">Copy video link to clipboard</span>
|
||||
<hr/>
|
||||
<span class="bsdn_contextMenuElement">OpenVK BSDN///Player 0.1</span>
|
||||
<hr/>
|
||||
<span class="bsdn_contextMenuElement">Developers:</span>
|
||||
<span class="bsdn_contextMenuElement" onclick="window.location.assign('https://github.com/celestora');">
|
||||
- celstora
|
||||
</span>
|
||||
<span class="bsdn_contextMenuElement" onclick="window.location.assign('https://github.com/openvk/openvk/issues');">
|
||||
- Report a problem...
|
||||
</span>
|
||||
<hr/>
|
||||
<span class="bsdn_contextMenuElement" onclick="confirm('эм это шутка');">About Adobe Flash Player...</span>
|
||||
</div>
|
||||
|
||||
<div class="bsdn_controls">
|
||||
<div>
|
||||
<button class="bsdn_playButton">
|
||||
<img src="/assets/packages/static/openvk/img/bsdn/play.png" style="padding-right: 2px; padding-top: 3px;">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bsdn_terebilkaWrap">
|
||||
<div class="bsdn_terebilkaUpperWrap">
|
||||
<img class="bsdn_logo" src="/assets/packages/static/openvk/img/bsdn/logo.gif" style="opacity: 0; /* TODO add logo xdd */" />
|
||||
<p class="bsdn_timeWrap">
|
||||
<time class="bsdn_timeReal">--:--</time>
|
||||
<time class="bsdn_timeFull">--:--</time>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bsdn_terebilkaLowerWrap">
|
||||
<div class="bsdn_terebilkaBrick"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<img class="bsdn_soundIcon" src="/assets/packages/static/openvk/img/bsdn/speaker.gif" />
|
||||
</div>
|
||||
|
||||
<div class="bsdn_soundControl">
|
||||
<div class="bsdn_soundControlPadding"></div>
|
||||
<div class="bsdn_soundControlSubWrap">
|
||||
<div class="bsdn_soundControlBrick" style="left: calc(100% - 10px);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="bsdn_fullScreenButton">
|
||||
<img src="/assets/packages/static/openvk/img/bsdn/fullscreen.gif" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bsdn_teaserWrap">
|
||||
<div class="bsdn_teaser">
|
||||
<div class="bsdn_teaserTitleBox">
|
||||
<b>${name}</b>
|
||||
<span>${author}</span>
|
||||
</div>
|
||||
|
||||
<div class="bsdn_teaserButton">
|
||||
<img src="/assets/packages/static/openvk/img/bsdn/play.png" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function _bsdnTerebilkaEventFactory(el, terebilka, callback, otherListeners) {
|
||||
let terebilkaSize = () => el.querySelector(terebilka).getBoundingClientRect().width; // чтобы просралось
|
||||
|
||||
let listeners = {
|
||||
mousemove: [
|
||||
e => {
|
||||
let buttonsPresseed = _bsdnUnwrapBitMask(e.buttons);
|
||||
if(!buttonsPresseed[0])
|
||||
return; // user doesn't click so nothing should be done
|
||||
|
||||
let offset = e.offsetX;
|
||||
let percents = Math.max(0, Math.min(100, offset / (terebilkaSize() / 100)));
|
||||
|
||||
return callback(percents);
|
||||
}
|
||||
],
|
||||
mousedown: [
|
||||
e => {
|
||||
let offset = e.offsetX;
|
||||
let percents = Math.max(0, Math.min(100, offset / (terebilkaSize() / 100)));
|
||||
|
||||
return callback(percents);
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
for(eventName in (otherListeners || {})) {
|
||||
if(listeners.hasOwnProperty(eventName))
|
||||
listeners[eventName] = otherListeners[eventName].concat(listeners[eventName]);
|
||||
else
|
||||
listeners[eventName] = otherListeners[eventName];
|
||||
}
|
||||
|
||||
return listeners;
|
||||
}
|
||||
|
||||
function _bsdnEventListenerFactory(el, v) {
|
||||
return {
|
||||
".bsdn-player": {
|
||||
click: [
|
||||
e => {
|
||||
if(el.querySelector(".bsdn_controls").contains(e.target) || el.querySelector(".bsdn_teaser").contains(e.target) || el.querySelector(".bsdn_contextMenu").contains(e.target))
|
||||
return;
|
||||
|
||||
if(el.querySelector(".bsdn_contextMenu").style.display !== "none") {
|
||||
el.querySelector(".bsdn_contextMenu").style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
if(v.paused)
|
||||
v.play();
|
||||
else
|
||||
v.pause();
|
||||
}
|
||||
],
|
||||
contextmenu: [
|
||||
e => {
|
||||
e.preventDefault();
|
||||
if(el.querySelector(".bsdn_controls").contains(e.target) || el.querySelector(".bsdn_contextMenu").contains(e.target))
|
||||
return;
|
||||
|
||||
let rect = el.querySelector(".bsdn-player").getBoundingClientRect();
|
||||
let h = rect.height, w = rect.width;
|
||||
let x, y;
|
||||
if(document.fullscreen) {
|
||||
x = e.screenX;
|
||||
y = e.screenY;
|
||||
} else {
|
||||
let rx = rect.x + window.scrollX, ry = rect.y + window.scrollY;
|
||||
x = e.pageX - rx;
|
||||
y = e.pageY - ry;
|
||||
}
|
||||
|
||||
if(h - y < 169)
|
||||
y = Math.max(0, y - 169);
|
||||
|
||||
if(w - x < 238)
|
||||
x = Math.max(0, x - 238);
|
||||
|
||||
let menu = el.querySelector(".bsdn_contextMenu");
|
||||
menu.style.top = y + "px";
|
||||
menu.style.left = x + "px";
|
||||
menu.style.display = "unset";
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
".bsdn_contextMenuElement": {
|
||||
click: [ () => el.querySelector(".bsdn_contextMenu").style.display = "none" ]
|
||||
},
|
||||
|
||||
".bsdn_video > video": {
|
||||
play: [
|
||||
() => {
|
||||
if(!el.querySelector(".bsdn-player").classList.contains("bsdn-dirty"))
|
||||
el.querySelector(".bsdn-player").classList.add("bsdn-dirty")
|
||||
|
||||
el.querySelector(".bsdn_playButton").innerHTML = "<img src='/assets/packages/static/openvk/img/bsdn/pause.gif' style='padding-right: 3px; padding-top: 3px;' />";
|
||||
el.querySelector(".bsdn-player").classList.add("_bsdn_playing");
|
||||
el.querySelector(".bsdn_teaserWrap").style.display = "none";
|
||||
}
|
||||
],
|
||||
pause: [
|
||||
() => {
|
||||
el.querySelector(".bsdn_playButton").innerHTML = "<img src='/assets/packages/static/openvk/img/bsdn/play.png' style='padding-right: 2px; padding-top: 3px; height: 19px;' />";
|
||||
el.querySelector(".bsdn-player").classList.remove("_bsdn_playing");
|
||||
el.querySelector(".bsdn_teaserWrap").style.display = "flex";
|
||||
}
|
||||
],
|
||||
timeupdate: [
|
||||
() => {
|
||||
el.querySelector(".bsdn_timeReal").innerHTML = _bsdnToHumanTime(v.currentTime);
|
||||
|
||||
let terebilkaSize = el.querySelector(".bsdn_terebilkaLowerWrap").getBoundingClientRect().width;
|
||||
let brickSize = 15;
|
||||
let percents = Math.ceil(v.currentTime / (v.duration / 100));
|
||||
let offset = ((terebilkaSize - brickSize) / 100) * percents;
|
||||
el.querySelector(".bsdn_terebilkaBrick").style.left = `min(calc(100% - 15px), ${offset}px`; // смешной мясной костыль ибо мне лень делать onresize
|
||||
}
|
||||
],
|
||||
volumechange: [
|
||||
() => {
|
||||
if(v.volume === 0)
|
||||
el.querySelector(".bsdn_soundIcon").src = "/assets/packages/static/openvk/img/bsdn/speaker_muted.gif";
|
||||
else
|
||||
el.querySelector(".bsdn_soundIcon").src = "/assets/packages/static/openvk/img/bsdn/speaker.gif";
|
||||
|
||||
let scSize = el.querySelector(".bsdn_soundControlSubWrap").getBoundingClientRect().width;
|
||||
let brickSize = 10;
|
||||
let offset = (scSize - brickSize) * v.volume;
|
||||
el.querySelector(".bsdn_soundControlBrick").style.left = offset + "px";
|
||||
}
|
||||
],
|
||||
loadedmetadata: [
|
||||
() => {
|
||||
el.querySelector(".bsdn_timeFull").innerHTML = _bsdnToHumanTime(v.duration);
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
".bsdn_fullScreenButton": {
|
||||
click: [
|
||||
() => {
|
||||
if(document.fullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
el.querySelector(".bsdn-player").requestFullscreen();
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
".bsdn_teaserButton|.bsdn_playButton": {
|
||||
click: [
|
||||
() => {
|
||||
if(v.paused)
|
||||
v.play();
|
||||
else
|
||||
v.pause();
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
".bsdn_terebilkaLowerWrap": _bsdnTerebilkaEventFactory(el, ".bsdn_terebilkaLowerWrap", function(p) {
|
||||
let time = (v.duration / 100) * p;
|
||||
setTimeout(() => {
|
||||
v.currentTime = time;
|
||||
if(v.currentTime === 0) {
|
||||
console.warn("[!] Хромог момент");
|
||||
console.warn("Теребилка не работает в хроме если сервер не реализует HTTP полностью.");
|
||||
console.warn("Встроенный сервер РНР не возвращает заголовки Accept-Range из-за чего хром отказывается seek'ать. Google как всегда.");
|
||||
console.warn("Установите Firefox для лучшей безопасности в сети: https://www.mozilla.org/ru/firefox/enterprise/#download");
|
||||
}
|
||||
}, 0);
|
||||
}, {
|
||||
mousedown: [
|
||||
e => v.pause()
|
||||
],
|
||||
mouseup: [
|
||||
e => v.play()
|
||||
]
|
||||
}),
|
||||
|
||||
".bsdn_soundControlSubWrap": _bsdnTerebilkaEventFactory(el, ".bsdn_soundControlSubWrap", function(p) {
|
||||
let volume = p / 100;
|
||||
v.volume = volume;
|
||||
}),
|
||||
|
||||
".bsdn_soundIcon": {
|
||||
click: [
|
||||
e => v.volume = v.volume === 0 ? 0.75 : 0
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _bsdnApplyBindings(el, v) {
|
||||
let listeners = _bsdnEventListenerFactory(el, v);
|
||||
|
||||
for(key in listeners) {
|
||||
let selectors = key.split("|");
|
||||
selectors.forEach(sel => {
|
||||
for(eventName in listeners[key]) {
|
||||
listeners[key][eventName].forEach(listener => {
|
||||
el.querySelectorAll(sel).forEach(target => {
|
||||
target.addEventListener(eventName, listener, {
|
||||
passive: (["contextmenu"]).indexOf(eventName) === -1
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function bsdnInitElement(el) {
|
||||
if(el.querySelector(".bdsn-hydrated") != null) {
|
||||
console.debug(el, " is already hydrated.");
|
||||
return;
|
||||
}
|
||||
|
||||
let video = el.querySelector("video");
|
||||
if(!video) {
|
||||
console.warning(el, " does not contain any <video>s.");
|
||||
return;
|
||||
}
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="bsdn-player bdsn-hydrated">
|
||||
${_bsdnTpl(el.dataset.name, el.dataset.author)}
|
||||
<div class="bsdn_video">
|
||||
${video.outerHTML}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
video = el.querySelector(".bsdn_video > video");
|
||||
_bsdnApplyBindings(el, video);
|
||||
video.volume = 0.75;
|
||||
}
|
||||
|
||||
function bsdnHydrate() {
|
||||
document.querySelectorAll(".bsdn").forEach(bsdnInitElement);
|
||||
}
|
|
@ -321,9 +321,9 @@ trim-extra-html-whitespace@1.3.0:
|
|||
integrity sha1-tH77DRpfKlaoXMRc6lJWUek0BM8=
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.28"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
||||
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
|
||||
version "0.7.33"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532"
|
||||
integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==
|
||||
|
||||
uglify-js@^3.1.4:
|
||||
version "3.17.3"
|
||||
|
|
|
@ -18,6 +18,7 @@ function _ovk_check_environment(): void
|
|||
|
||||
$requiredExtensions = [
|
||||
"gd",
|
||||
"imagick",
|
||||
"fileinfo",
|
||||
"PDO",
|
||||
"pdo_mysql",
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
<Clients name="standard">
|
||||
<Client tag="vk4me" name="VK4ME" url="http://vk4me.crx.moe/" img="/assets/packages/static/openvk/img/app_icons/vk4me.png" />
|
||||
<Client tag="openvk_legacy_android" name="OpenVK Legacy" url="https://f-droid.org/packages/uk.openvk.android.legacy/" img="/assets/packages/static/openvk/img/app_icons/openvk_legacy.png" />
|
||||
<Client tag="openvk_refresh_android" name="OpenVK Refresh" url="https://github.com/openvk/mobile-android-refresh" img="/assets/packages/static/openvk/img/app_icons/openvk_refresh.png" />
|
||||
</Clients>
|
|
@ -1,5 +1,5 @@
|
|||
"__locale" = "en_US.UTF-8;Eng";
|
||||
"__transNames" = "Russian-Latin/BGN; Any-Latin";
|
||||
"__transNames" = "[\P{script=Han}]; Russian-Latin/BGN; Any-Latin";
|
||||
|
||||
/* Check for https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html */
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
|||
"birth_date" = "Birth date";
|
||||
"registration_date" = "Registration date";
|
||||
"hometown" = "Hometown";
|
||||
"this_is_you" = "that is You";
|
||||
"this_is_you" = "it's you";
|
||||
"edit_page" = "Edit page";
|
||||
"edit_group" = "Edit group";
|
||||
"change_status" = "change status";
|
||||
|
@ -546,6 +546,7 @@
|
|||
"backdrop_succ" = "Backdrop settings saved";
|
||||
"backdrop_succ_rem" = "Backdrop images have been removed";
|
||||
"backdrop_succ_desc" = "Users will start seeing changes in 5 minutes.";
|
||||
"browse" = "Browse";
|
||||
|
||||
/* Two-factor authentication */
|
||||
|
||||
|
@ -620,15 +621,15 @@
|
|||
"notifications_post" = "$1 published $2a post$3 on your wall: $4";
|
||||
"notifications_appoint" = "$1 appointed you as community manager $2";
|
||||
|
||||
"nt_liked_yours" = "liked yours";
|
||||
"nt_shared_yours" = "shared yours";
|
||||
"nt_liked_yours" = "liked your";
|
||||
"nt_shared_yours" = "shared your";
|
||||
"nt_commented_yours" = "commented";
|
||||
"nt_written_on_your_wall" = "wrote on your wall";
|
||||
"nt_made_you_admin" = "appointed you in the community";
|
||||
|
||||
"nt_from" = "from";
|
||||
"nt_yours_adjective" = "yours";
|
||||
"nt_yours_feminitive_adjective" = "yours";
|
||||
"nt_yours_adjective" = "your";
|
||||
"nt_yours_feminitive_adjective" = "your";
|
||||
"nt_post_nominative" = "post";
|
||||
"nt_post_instrumental" = "post";
|
||||
"nt_note_instrumental" = "note";
|
||||
|
@ -992,6 +993,7 @@
|
|||
"error_upload_failed" = "Failed to upload a photo";
|
||||
"error_old_password" = "Old password does not match";
|
||||
"error_new_password" = "New password does not match";
|
||||
"error_weak_password" = "Password isn't strong enough. It should has at least 8 symbols, at least one capital letter and at least one digit.";
|
||||
"error_shorturl_incorrect" = "The short address has an incorrect format.";
|
||||
"error_repost_fail" = "Failed to share post";
|
||||
"error_data_too_big" = "Attribute '$1' must be at most $2 $3 long";
|
||||
|
@ -1197,6 +1199,7 @@
|
|||
/* User alerts */
|
||||
|
||||
"user_alert_scam" = "This account has been reported a lot for scam. Please be careful, especially if he asked for money.";
|
||||
"user_may_not_reply" = "This user may not reply to you because of your privacy settings. <a href='/settings?act=privacy'>Open privacy settings</a>";
|
||||
|
||||
/* Cookies pop-up */
|
||||
|
||||
|
@ -1416,4 +1419,4 @@
|
|||
|
||||
"tour_section_14_bottom_text_1" = "Screenshots";
|
||||
"tour_section_14_bottom_text_2" = "This concludes the tour of the site. If you want to try our mobile app, create your own group here, invite your friends or find new ones, or just have some fun in general, you can do it right now with a small <a href='/reg'>registration</a>";
|
||||
"tour_section_14_bottom_text_3" = "This concludes the tour of the site";
|
||||
"tour_section_14_bottom_text_3" = "This concludes the tour of the site";
|
||||
|
|
|
@ -556,6 +556,7 @@
|
|||
"end_all_sessions_description" = "Եթե ցանկանում եք դուրս գալ $1–ից ամեն դեվայսից, սեղմե՛ք ներքևի կոճակը";
|
||||
|
||||
"end_all_sessions_done" = "Բոլոր սեսսիաները նետված են, ներառյալ բջջային հավելվածները";
|
||||
"browse" = "Վերանայում";
|
||||
|
||||
/* Two-factor authentication */
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ list:
|
|||
- code: "uk"
|
||||
flag: "ua"
|
||||
name: "Ukrainian"
|
||||
native_name: "Україньска"
|
||||
author: "Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
|
||||
native_name: "Українcька"
|
||||
author: "Aqukie (yaroslav.bielograd@ukr.net), Andrej Lenťaj, Maxim Hrabovi (dechioyo) and Kirill (mbsoft)"
|
||||
- code: "by"
|
||||
flag: "by"
|
||||
name: "Belarussian"
|
||||
|
|
|
@ -506,6 +506,7 @@
|
|||
"backdrop_succ" = "Фон сохранён";
|
||||
"backdrop_succ_rem" = "Фон удалён";
|
||||
"backdrop_succ_desc" = "Изменения будут заметны другим пользователям через 5 минут.";
|
||||
"browse" = "Обзор";
|
||||
|
||||
/* Two-factor authentication */
|
||||
|
||||
|
@ -905,6 +906,7 @@
|
|||
"error_upload_failed" = "Не удалось загрузить фото";
|
||||
"error_old_password" = "Старый пароль не совпадает";
|
||||
"error_new_password" = "Новые пароли не совпадает";
|
||||
"error_weak_password" = "Ненадёжный пароль. Пароль должен содержать не менее 8 символов, цифры, прописные и строчные буквы";
|
||||
"error_shorturl_incorrect" = "Короткий адрес имеет некорректный формат.";
|
||||
"error_repost_fail" = "Не удалось поделиться записью";
|
||||
"error_data_too_big" = "Аттрибут '$1' не может быть длиннее $2 $3";
|
||||
|
@ -1085,6 +1087,7 @@
|
|||
/* User alerts */
|
||||
|
||||
"user_alert_scam" = "На этот аккаунт много жаловались в связи с мошенничеством. Пожалуйста, будьте осторожны, особенно если у вас попросят денег.";
|
||||
"user_may_not_reply" = "Этот пользователь, возможно, вам не сможет ответить из-за ваших настроек приватности. <a href='/settings?act=privacy'>Открыть настройки приватности</a>";
|
||||
|
||||
/* Cookies pop-up */
|
||||
|
||||
|
@ -1306,4 +1309,4 @@
|
|||
|
||||
"tour_section_14_bottom_text_1" = "Скриншоты приложения";
|
||||
"tour_section_14_bottom_text_2" = "На этом экскурсия по сайту завершена. Если вы хотите попробовать наше мобильное приложение, создать здесь свою группу, позвать своих друзей или найти новых, или вообще просто как-нибудь поразвлекаться, то это можно сделать прямо сейчас, пройдя небольшую <a href='/reg'>регистрацию</a>";
|
||||
"tour_section_14_bottom_text_3" = "На этом экскурсия по сайту завершена."
|
||||
"tour_section_14_bottom_text_3" = "На этом экскурсия по сайту завершена."
|
||||
|
|
|
@ -498,6 +498,20 @@
|
|||
"end_all_sessions_description" = "Якщо ви хочете вийти з $1 на всіх пристроях, натисніть кнопку нижче";
|
||||
"end_all_sessions_done" = "<b>Усі</b> сеанси було завершено";
|
||||
|
||||
"backdrop_short" = "Фон";
|
||||
"backdrop" = "Фон профілю";
|
||||
"backdrop_desc" = "Ви можете встановити два зображення як фон вашої сторінки. Вони будуть відображатися з обох боків у тих, хто зайде на вашу сторінку. За допомогою цієї можливості ви можете додати своєму профілю більше краси.";
|
||||
"backdrop_warn" = "Зображення будуть розташовані так, як на схемі вище. Їхня висота буде автоматично збільшена, щоб вони займали 100% висоти екрана, посередині буде розмиття";
|
||||
"backdrop_about_adding" = "За потребою, ви можете встановити тільки 1 зображення (але буде негарно), а також замінити тільки одне: якщо у вас уже стоїть два, а ви хочете замінити друге — то завантажуйте тільки друге, перше збережеться. Щоб видалити треба натиснути на відповідну кнопку внизу, видаляти по одній не можна.";
|
||||
"backdrop_save" = "Зберегти фон";
|
||||
"backdrop_remove" = "Видалити фон";
|
||||
"backdrop_error_title" = "Не вдалося зберегти фон";
|
||||
"backdrop_error_no_media" = "Зображення пошкоджені(-но) або завантажені не повністю";
|
||||
"backdrop_succ" = "Фон збережено";
|
||||
"backdrop_succ_rem" = "Фон видалено";
|
||||
"backdrop_succ_desc" = "Зміни будуть помітні іншим користувачам через 5 хвилин.";
|
||||
"browse" = "Огляд";
|
||||
|
||||
/* Two-factor authentication */
|
||||
|
||||
"two_factor_authentication" = "Двофакторна автентифікація";
|
||||
|
@ -578,6 +592,14 @@
|
|||
"nt_photo_instrumental" = "фотографією";
|
||||
"nt_topic_instrumental" = "темою";
|
||||
|
||||
"nt_you_were_mentioned_u" = "Вас згадав користувач";
|
||||
"nt_you_were_mentioned_g" = "Вас згадала група";
|
||||
"nt_mention_in_post_or_comms" = "у пості або під коментарями поста";
|
||||
"nt_mention_in_photo" = "в обговоренні фотографії";
|
||||
"nt_mention_in_video" = "в обговоренні відеозапису";
|
||||
"nt_mention_in_note" = "в обговоренні під";
|
||||
"nt_mention_in_topic" = "в обговоренні";
|
||||
|
||||
/* Time */
|
||||
|
||||
"time_at_sp" = " в ";
|
||||
|
@ -731,6 +753,8 @@
|
|||
"app_err_note" = "Не вдалося прикріпити замітку новин";
|
||||
"app_err_note_desc" = "Переконайтеся, що посилання правильне і нотатка належить вам.";
|
||||
|
||||
"learn_more" = "Детальніше";
|
||||
|
||||
/* Support */
|
||||
|
||||
"support_opened" = "Відкриті";
|
||||
|
@ -1116,28 +1140,28 @@
|
|||
"create_poll" = "Нове опитування";
|
||||
"poll_title" = "Тема опитування";
|
||||
"poll_add_option" = "Додати новий варіант відповіді...";
|
||||
"poll_anonymous" = "Анонiмне опитування";
|
||||
"poll_anonymous" = "Анонімне опитування";
|
||||
"poll_multiple" = "Множинний вибір";
|
||||
"poll_locked" = "Заборонити відміняти свій голос";
|
||||
"poll_locked" = "Заборонити скасовувати свій голос";
|
||||
"poll_edit_expires" = "Опитування закінчується через: ";
|
||||
"poll_edit_expires_days" = "днів";
|
||||
"poll_editor_tips" = "Натискання Backspace у пустому варианті приведе до його видалення. Tab/Enter у останньому додає новий.";
|
||||
"poll_editor_tips" = "Натискання Backspace у пустому варіанті приведе до його видалення. Tab/Enter у останньому додає новий.";
|
||||
"poll_embed" = "Отримати код";
|
||||
"poll_voter_count_zero" = "Станьте <b>першим</b>, хто проголосує!";
|
||||
"poll_voter_count_one" = "У опитуванні проголосовала <b>одна</b> людина.";
|
||||
"poll_voter_count_few" = "У опитуванні проголосувало <b>$1</b> людини.";
|
||||
"poll_voter_count_many" = "У опитуванні проголосувало <b>$1</b> людей.";
|
||||
"poll_voter_count_other" = "У опитуванні проголосувало <b>$1</b> людей.";
|
||||
"poll_voters_list" = "Список проголосувавших";
|
||||
"poll_voter_count_one" = "В опитуванні проголосувала <b>одна</b> людина.";
|
||||
"poll_voter_count_few" = "В опитуванні проголосувало <b>$1</b> людини.";
|
||||
"poll_voter_count_many" = "В опитуванні проголосувало <b>$1</b> людей.";
|
||||
"poll_voter_count_other" = "В опитуванні проголосувало <b>$1</b> людей.";
|
||||
"poll_voters_list" = "Список голосувальників";
|
||||
"poll_anon" = "Анонімне опитування";
|
||||
"poll_public" = "Публічне опитування";
|
||||
"poll_multi" = "багато вариантів";
|
||||
"poll_multi" = "багато варіантів";
|
||||
"poll_lock" = "не можна переголосувати";
|
||||
"poll_until" = "до $1";
|
||||
"poll_err_to_much_options" = "Занадто багато варіантів в опитуванні.";
|
||||
"poll_err_anonymous" = "Неможливо переглянути список тих, хто проголосував в анонімному голосуванні.";
|
||||
"cast_vote" = "Проголосувати!";
|
||||
"retract_vote" = "Відмінити голос";
|
||||
"retract_vote" = "Скасовувати голос";
|
||||
"attach_video" = "Прикріпити відео";
|
||||
"friends_list_other" = "Ви маєте $1 друга";
|
||||
"round_avatars" = "Круглі";
|
||||
|
@ -1153,6 +1177,154 @@
|
|||
"helpdesk_showing_name" = "Зображуване ім'я";
|
||||
"helpdesk_avatar_url" = "Посилання на аватар";
|
||||
"email_rate_limit_error" = "Не можна робити це так часто, вибачте.";
|
||||
"gifts_left_zero" = "Залишилося нуль подарунків";
|
||||
"gifts_left_zero" = "Залишилось 0 подарунків";
|
||||
"helpdesk_all_answers" = "всі відповіді";
|
||||
"helpdesk_show_number" = "Показувати номер";
|
||||
|
||||
/* Tutorial */
|
||||
"tour_title" = "Екскурс по сайту";
|
||||
"reg_title" = "Реєстрація";
|
||||
"ifnotlike_title" = " "А якщо мені не сподобається сайт?" ";
|
||||
"tour_promo" = "Про те, що Вас чекає після реєстрації";
|
||||
|
||||
"reg_text" = "<a href='/reg'>Реєстрація</a> акаунту абсолютно безоплатна та дуже легка";
|
||||
"ifnotlike_text" = "Ви завжди маєте змогу видалити свій акаунт";
|
||||
|
||||
"tour_next" = "Далі →";
|
||||
"tour_reg" = "Реєстрація →";
|
||||
|
||||
"tour_section_1" = "Початок";
|
||||
"tour_section_2" = "Профіль";
|
||||
"tour_section_3" = "Фотографії";
|
||||
"tour_section_4" = "Пошук";
|
||||
"tour_section_5" = "Відеозаписи";
|
||||
"tour_section_6" = "Аудіозаписи";
|
||||
"tour_section_7" = "Новинна стрічка";
|
||||
"tour_section_8" = "Глобальна стрічка";
|
||||
"tour_section_9" = "Групи";
|
||||
"tour_section_10" = "Події";
|
||||
"tour_section_11" = "Теми та дизайн";
|
||||
"tour_section_12" = "Стилістика";
|
||||
"tour_section_13" = "Ваучери";
|
||||
"tour_section_14" = "Мобільна версія";
|
||||
|
||||
"tour_section_1_title_1" = "З чого почати?";
|
||||
"tour_section_1_text_1" = "Реєстрація аккаунту є першим та фундаментальним етапом для початку вашого використання на цьому сайті";
|
||||
"tour_section_1_text_2" = "Для реєстрації нам знадобиться ім'я, E-Mail та пароль.";
|
||||
"tour_section_1_text_3" = "<b>Пам'ятайте:</b> Ваш E-mail буде використовуватися як логін для входу на сайт. Також ви можете не зазначати прізвище під час реєстрації. У разі втрати пароля для входу на сайт, скористайтеся розділом <a href='/restore'>відновлення</a>";
|
||||
"tour_section_1_bottom_text_1" = "Реєструючись на сайті, ви погоджуєтеся з <a href='/about'>правилами сайту</a> та <a href='/privacy'>політикою конфіденційності</a>";
|
||||
|
||||
"tour_section_2_title_1" = "Ваш профіль";
|
||||
"tour_section_2_text_1_1" = "Після реєстрації на сайті, ви автоматично потрапите у <b>свій</b> профіль";
|
||||
"tour_section_2_text_1_2" = "Ви зможете редагувати його де завгодно та в будь-який час, коли Ви самі цього бажаєте.";
|
||||
"tour_section_2_text_1_3" = "<b>Порада:</b> Щоб ваш профіль мав гарний і поважний вигляд, ви можете його заповнити будь-якою інформацією або завантажити фотографію, яка підкреслить, наприклад, ваш глибокий внутрішній світ.";
|
||||
"tour_section_2_bottom_text_1" = "Ви єдиний, хто вирішує, скільки інформації ваші друзі мають дізнатися про вас.";
|
||||
"tour_section_2_title_2" = "Задайте свої налаштування своєї приватності";
|
||||
"tour_section_2_text_2_1" = "Ви можете визначити, хто саме може мати доступ до певних типів інформації, розділам та можливостям зв'язатися з вами.";
|
||||
"tour_section_2_text_2_2" = "Ви маєте змогу закрити доступ до своєї сторінки від пошукових систем і незареєстрованих користувачів.";
|
||||
"tour_section_2_text_2_3" = "<b>Пам'ятайте:</b> у майбутньому налаштування приватності будуть розширюватися.";
|
||||
"tour_section_2_title_3" = "Персональна адреса сторінки";
|
||||
"tour_section_2_text_3_1" = "Після реєстрації сторінки, вам видається персональний ID типу: <b>/id12345</b>";
|
||||
"tour_section_2_text_3_2" = "<b>Стандартний ID</b>, який був отриманий після реєстрації, <b>змінити не можна!</b>";
|
||||
"tour_section_2_text_3_3" = "Однак, в налаштуваннях своєї сторінки ви зможете прив'язати свою персональну адресу і цю адресу <b>можна буде змінити</b> в будь-який час";
|
||||
"tour_section_2_text_3_4" = "<b>Порада:</b> Можна займати будь-яку вільну адресу, довжина якої не менша за 5 символів.";
|
||||
"tour_section_2_bottom_text_2" = "<i>Підтримується встановлення будь-якої короткої адреси з латинських маленьких літер; адреса може містити цифри (однак, не на початку), крапки та нижні підкреслення (не на початку або наприкінці)</i>";
|
||||
"tour_section_2_title_4" = "Стіна";
|
||||
|
||||
"tour_section_3_title_1" = "Діліться своїми фотографіями";
|
||||
"tour_section_3_text_1" = "Розділ "Фотографії" доступний у вашому профілі одразу з моменту реєстрації.";
|
||||
"tour_section_3_text_2" = "Ви можете переглядати фотоальбоми користувачів та створювати свої власні фотоальбоми.";
|
||||
"tour_section_3_text_3" = "Доступ до всіх ваших фотоальбомів для інших користувачів редагується в налаштуваннях приватності.";
|
||||
"tour_section_3_bottom_text_1" = "Ви можете створювати необмежену кількість фотоальбомів з ваших подорожей або будь-яких подій, або просто зберігати фото кошенят";
|
||||
|
||||
"tour_section_4_title_1" = "Пошук";
|
||||
"tour_section_4_text_1" = "Розділ "Пошук" має на меті шукати користувачів та групи.";
|
||||
"tour_section_4_text_2" = "Даний розділ сайту з часом буде покращуватися та збільшуватися.";
|
||||
"tour_section_4_text_3" = "Для початку пошуку, треба знати ім'я (чи прізвище); а якщо шукаєте групу, то потрібно знати її назву.";
|
||||
"tour_section_4_title_2" = "Швидкий пошук";
|
||||
"tour_section_4_text_4" = "Якщо Ви бажаєте будь-яким чином зберегти власний час, то поле пошуку доступна і в шапці сайту";
|
||||
|
||||
"tour_section_5_title_1" = "Завантажуйте та діліться відео зі своїми друзями!";
|
||||
"tour_section_5_text_1" = "Ви можете завантажувати необмежену кількість відеозаписів і кліпів";
|
||||
"tour_section_5_text_2" = "Розділ "Відеозаписи" регулюється налаштуваннями приватності";
|
||||
"tour_section_5_bottom_text_1" = "Відео можна завантажувати, обходячи розділ "Відеозаписи" через звичайне прикріплення до нового допису на стіні:";
|
||||
"tour_section_5_title_2" = "Імпорт відео з YouTube";
|
||||
"tour_section_5_text_3" = "Окрім завантаження відео напряму, сайт підтримує і вбудовані відео через YouTube";
|
||||
|
||||
"tour_section_6_title_1" = "Аудіозаписи";
|
||||
"tour_section_6_text_1" = "!!! АУДІОЗАПИСІВ НЕМАЄ, ЧЕКАЙТЕ ІНФОРМАЦІЇ ВІД ГЕН.ШТАБУ !!!";
|
||||
|
||||
"tour_section_7_title_1" = "Слідкуйте за тим, що пишуть ваші друзі";
|
||||
"tour_section_7_text_1" = "Розділ "Мої Новини" поділяється на два типи: локальна стрічка та глобальна стрічка новин";
|
||||
"tour_section_7_text_2" = "У локальній стрічці будуть показуватися новини тільки ваших друзів і груп";
|
||||
"tour_section_7_bottom_text_1" = "Ніякої системи рекомендацій. <b>Свою стрічку новин формуєте тільки ви.</b>";
|
||||
|
||||
"tour_section_8_title_1" = "Слідкуйте за тим, які теми обговорюють на сайті";
|
||||
"tour_section_8_text_1" = "У глобальній стрічці будуть показуватися дописи всіх користувачів сайту та груп";
|
||||
"tour_section_8_text_2" = "Перегляд цього розділу може не рекомендуватися для чутливих і вразливих людей";
|
||||
"tour_section_8_bottom_text_1" = "Дизайн глобальної стрічки за дизайном ніяк не відрізняється від локальної";
|
||||
"tour_section_8_bottom_text_2" = "У стрічці є безліч типів контенту: починаючи від звичайних фото і відео, і закінчуючи анонімними постами й опитуваннями";
|
||||
|
||||
"tour_section_9_text_1" = "На сайті безліч груп, присвячені різним темам";
|
||||
"tour_section_9_text_2" = "Ви можете приєднатися до будь-якої групи. А якщо не знайшли відповідну, то можна створити власну";
|
||||
"tour_section_9_text_3" = "Кожна група може мати в собі свій розділ вікі-сторінок, фотоальбомів, блок посилань та обговорень";
|
||||
"tour_section_9_title_2" = "Ви можете керувати своєю групою разом із другом";
|
||||
"tour_section_9_text_2_1" = "Керування групою здійснюється в розділі "Редагувати групу" під аватаром спільноти";
|
||||
"tour_section_9_text_2_2" = "Створіть команду адміністраторів зі звичайних учасників або тих, кому ви довіряєте";
|
||||
"tour_section_9_text_2_3" = "Ви можете приховати потрібного Вам адміністратора, щоб він не показувався в межах вашої групи";
|
||||
"tour_section_9_bottom_text_1" = "Розділ "Мої Групи" знаходиться в лівому меню сайту";
|
||||
"tour_section_9_bottom_text_2" = "Приклад спільноти";
|
||||
"tour_section_9_bottom_text_3" = "Групи часто являють собою реальні організації, члени яких хочуть залишатися на зв'язку зі своєю аудиторією";
|
||||
|
||||
"tour_section_10_title_1" = "Овва!";
|
||||
"tour_section_10_text_1" = "Цей розділ поки що не написаний. Пропустіть його.";
|
||||
|
||||
"tour_section_11_title_1" = "Теми оформлення";
|
||||
"tour_section_11_text_1" = "Після реєстрації, як стандартна тема, у вас буде класична тема оформлення";
|
||||
"tour_section_11_text_2" = "Деяких нових користувачів може налякати чинна класична тема, яка виглядає дуже архаїчно";
|
||||
"tour_section_11_text_3" = "<b>Але не біда:</b> Ви можете обрати іншу тему з каталогу, або створити свою, ознаомившись з <a href='https://docs.openvk.uk/'>документацією</a>";
|
||||
"tour_section_11_bottom_text_1" = "Каталог тем доступний в розділі "Мої Налаштування" в вкладці "Інтерфейс" ";
|
||||
"tour_section_11_wordart" = "<img src='https://openvk.uk/assets/packages/static/openvk/img/tour/wordart.png' width='65%'>";
|
||||
|
||||
"tour_section_12_title_1" = "Фон профілю та групи";
|
||||
"tour_section_12_text_1" = "Ви можете встановити одну чи два зображення як фон вашої сторінки";
|
||||
"tour_section_12_text_2" = "Вони відображатимуться по боках у тих, хто зайде на вашу сторінку";
|
||||
"tour_section_12_text_3" = "<b>Порада:</b> перед встановленням фону, поекспериментуйте з розміткою: спробуйте віддзеркалити майбутню фонову картинку, або взагалі просто створіть гарний градієнт";
|
||||
"tour_section_12_title_2" = "Аватари";
|
||||
"tour_section_12_text_2_1" = "Ви можете задати варіант показу аватара користувача: стандартне, заокруглені та квадратні (1:1)";
|
||||
"tour_section_12_text_2_2" = "Ці налаштування буде видно тільки вам";
|
||||
"tour_section_12_title_3" = "Редагування правого меню";
|
||||
"tour_section_12_text_3_1" = "За потреби, ви можете приховати непотрібні розділи сайту";
|
||||
"tour_section_12_text_3_2" = "<b>Нагадування: </b>Розділи першої необхідності (Моя Сторінка; Мої Друзі; Мої Відповіді; Мої Налаштування) приховати не можна";
|
||||
"tour_section_12_title_4" = "Вид постів";
|
||||
"tour_section_12_text_4_1" = "Якщо набрид старий дизайн стіни, який був у колись популярному оригінальному ВКонтактє.сру, то ви завжди можете змінити вигляд постів на Мікроблог";
|
||||
"tour_section_12_text_4_2" = "Вид постів можна змінювати між двома варіантами в будь-який час";
|
||||
"tour_section_12_text_4_3" = "<b>Зверніть увагу</b>, що якщо обрано старий вид відображення постів, то останні коментарі довантажуватися не будуть";
|
||||
"tour_section_12_bottom_text_1" = "Сторінка встановлення фону";
|
||||
"tour_section_12_bottom_text_2" = "Приклади сторінок зі встановленим фоном";
|
||||
"tour_section_12_bottom_text_3" = "За допомогою цієї можливості ви можете додати своєму профілю більше індивідуальності";
|
||||
"tour_section_12_bottom_text_4" = "Старий вигляд постів";
|
||||
"tour_section_12_bottom_text_5" = "Мікроблок";
|
||||
|
||||
"tour_section_13_title_1" = "Ваучер";
|
||||
"tour_section_13_text_1" = "Ваучер в OpenVK це щось на кшталт промокоду на додавання будь-якої валюти (відсотки рейтингу, голосів тощо)";
|
||||
"tour_section_13_text_2" = "Подібні купони створюються за будь-якими значущими подіями та святами. Слідкуйте за <a href='https://t.me/openvk'>Telegram каналом</a> OpenVK";
|
||||
"tour_section_13_text_3" = "Після активації будь-якого ваучера, задана адміністраторами валюта буде перечислена в вашу користь";
|
||||
"tour_section_13_text_4" = "<b>Пам'ятайте: </b>Усі ваучери мають обмежений термін активації";
|
||||
"tour_section_13_bottom_text_1" = "Ваучери складаються з 24 цифр та літер";
|
||||
"tour_section_13_bottom_text_2" = "Активація пройшла вдало (наприклад, нам зарахували 100 голосів)";
|
||||
"tour_section_13_bottom_text_3" = "<b>Увага: </b>Після активації ваучера на вашу сторінку, той самий ваучер не можна буде активувати повторно";
|
||||
|
||||
"tour_section_14_title_1" = "Мобільна версія";
|
||||
"tour_section_14_text_1" = "Наразі мобільної вебверсії сайту поки що немає, проте є мобільний додаток для Android";
|
||||
"tour_section_14_text_2" = "OpenVK Legacy - це мобільний додаток OpenVK для пристроїв на базі Android із дизайном ВКонтакте 3.0.4 2013 року";
|
||||
"tour_section_14_text_3" = "Мінімально підтримуваною версією є Android 2.1 Eclair, тобто апарати часів початку 2010-их стануть у пригоді.";
|
||||
|
||||
"tour_section_14_title_2" = "Де це можна завантажити?";
|
||||
"tour_section_14_text_2_1" = "Релізні версії завантажуються через офіційний репозиторій F-Droid";
|
||||
"tour_section_14_text_2_2" = "Якщо ви є бета-тестувальником програми, то нові версії програми викладаються в окремий канал оновлення";
|
||||
"tour_section_14_text_2_3" = "<b>Важливо: </b>Додаток може мати різні помилки та недоліки, про помилки повідомляйте в <a href='/app'>офіційну групу додатка</a>";
|
||||
|
||||
"tour_section_14_bottom_text_1" = "Скріншоти застосунку";
|
||||
"tour_section_14_bottom_text_2" = "На цьому екскурсія сайтом завершена. Якщо ви хочете спробувати наш мобільний застосунок, створити тут свою групу, покликати своїх друзів чи знайти нових, або взагалі просто якось розважитися, то це можна зробити просто зараз, пройшовши невелику <a href='/reg'>реєстрацію</a>";
|
||||
"tour_section_14_bottom_text_3" = "На цьому екскурсія сайтом завершена.";
|
||||
|
|
|
@ -27,6 +27,7 @@ openvk:
|
|||
requirePhone: false
|
||||
forcePhoneVerification: false
|
||||
forceEmailVerification: false
|
||||
forceStrongPassword: false
|
||||
enableSu: true
|
||||
rateLimits:
|
||||
actions: 5
|
||||
|
|