diff --git a/README.md b/README.md index 43f8e958..aee95755 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ Updating the source code is done with this command: `git pull` ## Instances * **[openvk.su](https://openvk.su/)** +* **[openvk.uk](https://openvk.uk)** - official mirror of openvk.su () * [social.fetbuk.ru](http://social.fetbuk.ru/) -* [openvk.zavsc.pw](https://openvk.zavsc.pw/) ## Can I create my own OpenVK instance? diff --git a/README_RU.md b/README_RU.md index 65b0dbd6..47a06202 100644 --- a/README_RU.md +++ b/README_RU.md @@ -17,8 +17,8 @@ _[English](README.md)_ ## Инстанции * **[openvk.su](https://openvk.su/)** +* **[openvk.uk](https://openvk.uk)** - официальное зеркало openvk.su () * [social.fetbuk.ru](http://social.fetbuk.ru/) -* [openvk.zavsc.pw](https://openvk.zavsc.pw/) ## Могу ли я создать свою собственную инстанцию OpenVK? @@ -80,8 +80,8 @@ ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions * [Обсуждения](https://github.com/openvk/openvk/discussions) * Чат в Matrix: #ovk:matrix.org -**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [at] tutanota [dot] com**. +**Внимание**: баг-трекер, телеграм- и matrix-чат являются публичными местами, и жалобы в OVK обслуживается волонтерами. Если вам нужно сообщить о чем-то, что не должно быть раскрыто широкой публике (например, сообщение об уязвимости), пожалуйста, свяжитесь с нами напрямую по этому адресу: **openvk [собака] tutanota [точка] com**. - Get it on Codeberg + Получить на Codeberg diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index 21f4c93b..eb5d3230 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -88,4 +88,100 @@ final class Groups extends VKAPIRequestHandler "items" => $rClubs ]; } + + function getById(string $group_ids = "", string $group_id = "", string $fields = ""): ?array + { + $this->requireUser(); + + $clubs = new ClubsRepo; + + if ($group_ids == null && $group_id != null) + $group_ids = $group_id; + + if ($group_ids == null && $group_id == null) + $this->fail(100, "One of the parameters specified was missing or invalid: group_ids is undefined"); + + $clbs = explode(',', $group_ids); + $response; + + $ic = sizeof($clbs); + + for ($i=0; $i < $ic; $i++) { + if($i > 500) + break; + + if($clbs[$i] < 0) + $this->fail(100, "ты ошибся чутка, у айди группы убери минус"); + + $clb = $clubs->get((int) $clbs[$i]); + if(is_null($clb)) + { + $response[$i] = (object)[ + "id" => intval($clbs[$i]), + "name" => "DELETED", + "screen_name" => "club".intval($clbs[$i]), + "type" => "group", + "description" => "This group was deleted or it doesn't exist" + ]; + }else if($clbs[$i] == null){ + + }else{ + $response[$i] = (object)[ + "id" => $clb->getId(), + "name" => $clb->getName(), + "screen_name" => $clb->getShortCode() ?? "club".$clb->getId(), + "is_closed" => false, + "type" => "group", + "can_access_closed" => true, + ]; + + $flds = explode(',', $fields); + + foreach($flds as $field) { + switch ($field) { + case 'verified': + $response[$i]->verified = intval($clb->isVerified()); + break; + case 'has_photo': + $response[$i]->has_photo = is_null($clb->getAvatarPhoto()) ? 0 : 1; + break; + case 'photo_max_orig': + $response[$i]->photo_max_orig = $clb->getAvatarURL(); + break; + case 'photo_max': + $response[$i]->photo_max = $clb->getAvatarURL(); + break; + case 'members_count': + $response[$i]->members_count = $clb->getFollowersCount(); + break; + case 'site': + $response[$i]->site = $clb->getWebsite(); + break; + case 'description': + $response[$i]->desctiption = $clb->getDescription(); + break; + case 'contacts': + $contacts; + $contactTmp = $clb->getManagers(1, true); + foreach($contactTmp as $contact) { + $contacts[] = array( + 'user_id' => $contact->getUser()->getId(), + 'desc' => $contact->getComment() + ); + } + $response[$i]->contacts = $contacts; + break; + case 'can_post': + if($clb->canBeModifiedBy($this->getUser())) + $response[$i]->can_post = true; + else + $response[$i]->can_post = $clb->canPost(); + break; + } + } + } + } + + return $response; + } } diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php new file mode 100644 index 00000000..dd4fd436 --- /dev/null +++ b/VKAPI/Handlers/Newsfeed.php @@ -0,0 +1,42 @@ +requireUser(); + + if($offset != 0) $start_from = $offset; + + $id = $this->getUser()->getId(); + $subs = DatabaseConnection::i() + ->getContext() + ->table("subscriptions") + ->where("follower", $id); + $ids = array_map(function($rel) { + return $rel->target * ($rel->model === "openvk\Web\Models\Entities\User" ? 1 : -1); + }, iterator_to_array($subs)); + $ids[] = $this->getUser()->getId(); + + $posts = DatabaseConnection::i() + ->getContext() + ->table("posts") + ->select("id") + ->where("wall IN (?)", $ids) + ->where("deleted", 0) + ->order("created DESC"); + + $rposts = []; + foreach($posts->page((int) ($offset + 1), $count) as $post) + $rposts[] = (new PostsRepo)->get($post->id)->getPrettyId(); + + return (new Wall)->getById(implode(',', $rposts), $extended, $fields, $this->getUser()); + } +} diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 530c8aa4..ec153695 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -110,6 +110,110 @@ final class Wall extends VKAPIRequestHandler ]; } + function getById(string $posts, int $extended = 0, string $fields = "", User $user = null) + { + if($user == null) $user = $this->getUser(); // костыли костыли крылышки + + $items = []; + $profiles = []; + $groups = []; + # $count = $posts->getPostCountOnUserWall((int) $owner_id); + + $psts = explode(",", $posts); + + foreach($psts as $pst) + { + $id = explode("_", $pst); + $post = (new PostsRepo)->getPostById(intval($id[0]), intval($id[1])); + if($post) { + $from_id = get_class($post->getOwner()) == "openvk\Web\Models\Entities\Club" ? $post->getOwner()->getId() * (-1) : $post->getOwner()->getId(); + $items[] = (object)[ + "id" => $post->getVirtualId(), + "from_id" => $from_id, + "owner_id" => $post->getTargetWall(), + "date" => $post->getPublicationTime()->timestamp(), + "post_type" => "post", + "text" => $post->getText(), + "can_edit" => 0, // TODO + "can_delete" => $post->canBeDeletedBy($user), + "can_pin" => $post->canBePinnedBy($user), + "can_archive" => false, // TODO MAYBE + "is_archived" => false, + "is_pinned" => $post->isPinned(), + "post_source" => (object)["type" => "vk"], + "comments" => (object)[ + "count" => $post->getCommentsCount(), + "can_post" => 1 + ], + "likes" => (object)[ + "count" => $post->getLikesCount(), + "user_likes" => (int) $post->hasLikeFrom($user), + "can_like" => 1, + "can_publish" => 1, + ], + "reposts" => (object)[ + "count" => $post->getRepostCount(), + "user_reposted" => 0 + ] + ]; + + if ($from_id > 0) + $profiles[] = $from_id; + else + $groups[] = $from_id * -1; + } + } + + if($extended == 1) + { + $profiles = array_unique($profiles); + $groups = array_unique($groups); + + $profilesFormatted = []; + $groupsFormatted = []; + + foreach ($profiles as $prof) { + $user = (new UsersRepo)->get($prof); + $profilesFormatted[] = (object)[ + "first_name" => $user->getFirstName(), + "id" => $user->getId(), + "last_name" => $user->getLastName(), + "can_access_closed" => false, + "is_closed" => false, + "sex" => $user->isFemale() ? 1 : 2, + "screen_name" => $user->getShortCode(), + "photo_50" => $user->getAvatarUrl(), + "photo_100" => $user->getAvatarUrl(), + "online" => $user->isOnline() + ]; + } + + foreach($groups as $g) { + $group = (new ClubsRepo)->get($g); + $groupsFormatted[] = (object)[ + "id" => $group->getId(), + "name" => $group->getName(), + "screen_name" => $group->getShortCode(), + "is_closed" => 0, + "type" => "group", + "photo_50" => $group->getAvatarUrl(), + "photo_100" => $group->getAvatarUrl(), + "photo_200" => $group->getAvatarUrl(), + ]; + } + + return (object)[ + "items" => (array)$items, + "profiles" => (array)$profilesFormatted, + "groups" => (array)$groupsFormatted + ]; + } + else + return (object)[ + "items" => (array)$items + ]; + } + function post(string $owner_id, string $message = "", int $from_group = 0, int $signed = 0): object { $this->requireUser(); diff --git a/Web/Models/Entities/Note.php b/Web/Models/Entities/Note.php index b33f1238..762ebef8 100644 --- a/Web/Models/Entities/Note.php +++ b/Web/Models/Entities/Note.php @@ -48,6 +48,7 @@ class Note extends Postable "acronym", "blockquote", "cite", + "span", ]); $config->set("HTML.AllowedAttributes", [ "table.summary", @@ -59,6 +60,8 @@ class Note extends Postable "img.style", "div.style", "div.title", + "span.class", + "p.class", ]); $config->set("CSS.AllowedProperties", [ "float", @@ -68,6 +71,9 @@ class Note extends Postable "max-width", "font-weight", ]); + $config->set("Attr.AllowedClasses", [ + "underline", + ]); $purifier = new HTMLPurifier($config); return $purifier->purify($this->getRecord()->source); diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index 17500e8d..f57159b7 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -41,7 +41,9 @@ class Video extends Media mkdir($dirId); $dir = $this->getBaseDir(); - Shell::bash(__DIR__ . "/../shell/processVideo.sh", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD + $ext = Shell::isPowershell() ? "ps1" : "sh"; + $cmd = Shell::isPowershell() ? "powershell" : "bash"; + Shell::$cmd(__DIR__ . "/../shell/processVideo.$ext", OPENVK_ROOT, $filename, $dir, $hash)->start(); #async :DDD } catch(ShellUnavailableException $suex) { exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "Shell is unavailable" : VIDEOS_FRIENDLY_ERROR); } catch(UnknownCommandException $ucex) { diff --git a/Web/Models/Repositories/Notes.php b/Web/Models/Repositories/Notes.php index f1b19eaf..a41c6914 100644 --- a/Web/Models/Repositories/Notes.php +++ b/Web/Models/Repositories/Notes.php @@ -29,7 +29,7 @@ class Notes function getUserNotes(User $user, int $page = 1, ?int $perPage = NULL): \Traversable { $perPage = $perPage ?? OPENVK_DEFAULT_PER_PAGE; - foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->page($page, $perPage) as $album) + foreach($this->notes->where("owner", $user->getId())->where("deleted", 0)->order("created DESC")->page($page, $perPage) as $album) yield new Note($album); } diff --git a/Web/Models/shell/processVideo.ps1 b/Web/Models/shell/processVideo.ps1 new file mode 100644 index 00000000..4ad382dd --- /dev/null +++ b/Web/Models/shell/processVideo.ps1 @@ -0,0 +1,20 @@ +$ovkRoot = $args[0] +$file = $args[1] +$dir = $args[2] +$hash = $args[3] +$hashT = $hash.substring(0, 2) +$temp = [System.IO.Path]::GetTempFileName() +$temp2 = [System.IO.Path]::GetTempFileName() + +$shell = Get-WmiObject Win32_process -filter "ProcessId = $PID" +$shell.SetPriority(16384) + +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=640x360,setsar=1:1 -y $temp2 + +Move-Item $temp2 "$dir$hashT/$hash.ogv" +Remove-Item $temp +Remove-Item $temp2 diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index c3765d82..13766acb 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -31,7 +31,7 @@ final class UserPresenter extends OpenVKPresenter { $user = $this->users->get($id); if(!$user || $user->isDeleted()) - $this->notFound(); + $this->template->_template = "User/deleted.xml"; else { if($user->getShortCode()) if(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH) !== "/" . $user->getShortCode()) diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index a40e667b..185af91f 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -98,8 +98,8 @@
-