Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a205f50
start preparing to add messenger attachments
fgRuslan Feb 3, 2026
dbb9b1f
try to fix video player in messages
fgRuslan Feb 4, 2026
bee3dd5
fix attachment width in messenger
fgRuslan Feb 4, 2026
f0aa83a
hopefully fix notes and documents
fgRuslan Feb 4, 2026
e61cb63
make attachments modal
fgRuslan Feb 4, 2026
4715c24
fix not found error for videos and fix note modal displaying
fgRuslan Feb 4, 2026
c00ca88
fix notes and document localization and fix note modal view
fgRuslan Feb 5, 2026
ff0152f
start adding attachment menu in messenger
fgRuslan Feb 5, 2026
4b81bef
fix document rendering and previews
fgRuslan Feb 4, 2026
e3f59e4
fix removing preview items
fgRuslan Feb 5, 2026
ec5da08
fix video player population with controls
fgRuslan Feb 6, 2026
875e79b
implement audio attaching
fgRuslan Feb 7, 2026
d05b0b3
fix messenger video hydration and note displaying
fgRuslan Feb 11, 2026
79bf697
fix attachment menu toggler position
fgRuslan Feb 12, 2026
3d8f237
fix removing attachments from the hidden form
fgRuslan Feb 12, 2026
ceef6e7
make attachment preview include type attribute
fgRuslan Feb 14, 2026
acdca6d
ублажаю линтер
fgRuslan Feb 14, 2026
6cc09b8
да капец
fgRuslan Feb 14, 2026
e1c13f2
prevent message attachments from being publicly accessible
fgRuslan Apr 25, 2026
093991a
линтер опять это самое
fgRuslan Apr 25, 2026
1c8ae51
fix migration number
fgRuslan Apr 25, 2026
b9e1ffb
fix youtube video rendering in messenger
fgRuslan May 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion VKAPI/Handlers/Messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ public function send(
} elseif (!empty($attachment)) {
$attachs = parseAttachments($attachment);

# Работают только фотки, остальное просто не будет отображаться.
if (sizeof($attachs) >= 10) {
$this->fail(15, "Too many attachments");
}
Expand Down
66 changes: 63 additions & 3 deletions Web/Models/Entities/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Repositories\Clubs;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Entities\Photo;
use openvk\Web\Models\Entities\{Photo, Video, Audio, Note, Document};
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;

Expand Down Expand Up @@ -131,17 +131,77 @@ public function simplify(): array
$attachments[] = [
"type" => "photo",
"link" => "/photo" . $attachment->getPrettyId(),
"id" => $attachment->getPrettyId(),
"photo" => [
"url" => $attachment->getURL(),
"caption" => $attachment->getDescription(),
],
];
} elseif ($attachment instanceof Video) {
if ($attachment->getType() != 1) {
$videoUrl = $attachment->getURL();
$embedHtml = null;
} else {
$videoUrl = $attachment->getVideoDriver()->getURL();
$embedHtml = $attachment->getVideoDriver()->getEmbed("100%");
}
$attachments[] = [
"type" => "video",
"link" => "/video" . $attachment->getPrettyId(),
"id" => $attachment->getOwner()->getId() . "_" . $attachment->getVirtualId(),
"video" => [
"url" => $videoUrl,
"embed_html" => $embedHtml,
"name" => $attachment->getName(),
"length" => $attachment->getLength(),
"formatted_length" => $attachment->getFormattedLength(),
"thumbnail" => $attachment->getThumbnailURL(),
"author" => $attachment->getOwner()->getCanonicalName(),
],
];
} elseif ($attachment instanceof Audio) {
$attachments[] = [
"type" => "audio",
"link" => "/audio" . $attachment->getPrettyId(),
"audio" => [
"name" => $attachment->getName(),
"artist" => $attachment->getPerformer(),
],
];
} elseif ($attachment instanceof Note) {
$attachments[] = [
"type" => "note",
"link" => "/note" . $attachment->getId(),
"id" => $attachment->getId(),
"note" => [
"name" => $attachment->getName(),
],
];
} elseif ($attachment instanceof Document) {
$previewData = null;
if ($attachment->hasPreview()) {
$previewData = [
"tiny" => $attachment->getPreview()->getURLBySizeId('tiny'),
"medium" => $attachment->getPreview()->getURLBySizeId('medium'),
];
}

$attachments[] = [
"type" => "doc",
"link" => "/doc" . $attachment->getPrettyId(),
"id" => $attachment->getPrettyId(),
"document" => [
"name" => $attachment->getName(),
"ext" => $attachment->getFileExtension(),
"size_str" => readable_filesize($attachment->getFilesize()),
"preview" => $previewData,
"pub_time" => (string) $attachment->getPublicationTime(),
],
];
} else {
$attachments[] = [
"type" => "unknown",
];

# throw new \Exception("Unknown attachment type: " . get_class($attachment));
}
}

Expand Down
18 changes: 18 additions & 0 deletions Web/Models/Entities/Photo.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
use Nette\Utils\UnknownImageFileException;
use openvk\Web\Models\Entities\Album;
use openvk\Web\Models\Repositories\Albums;
use openvk\Web\Models\Entities\Traits\TMessageAttachment;
use Chandler\Database\DatabaseConnection as DB;
use Nette\InvalidStateException as ISE;
use Nette\Utils\Image;

class Photo extends Media
{
use TMessageAttachment;

protected $tableName = "photos";
protected $fileExtension = "jpeg";

Expand Down Expand Up @@ -381,12 +384,27 @@ public function toVkApiStruct(bool $photo_sizes = true, bool $extended = false):
return $res;
}

public function isMessagePhoto(): bool
{
return (bool) $this->getRecord()->is_message_photo;
}

public function canBeViewedBy(?User $user = null): bool
{
if ($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}

if ($this->isMessagePhoto()) {
if (!$user) {
return false;
}
if ($user->getId() === $this->getOwner()->getId()) {
return true;
}
return $this->isViewableByMessageParticipant($user);
}

if (!is_null($this->getAlbum())) {
return $this->getAlbum()->canBeViewedBy($user);
} else {
Expand Down
45 changes: 45 additions & 0 deletions Web/Models/Entities/Traits/TMessageAttachment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace openvk\Web\Models\Entities\Traits;

use openvk\Web\Models\Entities\User;
use Chandler\Database\DatabaseConnection;

trait TMessageAttachment
{
private function isViewableByMessageParticipant(?User $user): bool
{
if (!$user) {
return false;
}

$db = DatabaseConnection::i()->getContext();
$msgIds = [];

foreach ($db->table("attachments")
->select("target_id")
->where("attachable_type", get_class($this))
->where("attachable_id", $this->getId())
->where("target_type", "openvk\\Web\\Models\\Entities\\Message") as $row) {
$msgIds[] = $row->target_id;
}

if (empty($msgIds)) {
return false;
}

return (bool) $db->table("messages")
->where("id", $msgIds)
->where("deleted", 0)
->where(
"(sender_type = ? AND sender_id = ?) OR (recipient_type = ? AND recipient_id = ?)",
"openvk\\Web\\Models\\Entities\\User",
$user->getId(),
"openvk\\Web\\Models\\Entities\\User",
$user->getId()
)
->count("*");
}
}
18 changes: 18 additions & 0 deletions Web/Models/Entities/Video.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use openvk\Web\Util\Shell\Shell;
use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandException};
use openvk\Web\Models\VideoDrivers\VideoDriver;
use openvk\Web\Models\Entities\Traits\TMessageAttachment;
use Nette\InvalidStateException as ISE;

define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<");

class Video extends Media
{
use TMessageAttachment;

public const TYPE_DIRECT = 0;
public const TYPE_EMBED = 1;
public const TYPE_UNKNOWN = -1;
Expand Down Expand Up @@ -339,12 +342,27 @@ public function getPageURL(): string
return "/video" . $this->getPrettyId();
}

public function isMessageVideo(): bool
{
return (bool) $this->getRecord()->is_message_video;
}

public function canBeViewedBy(?User $user = null): bool
{
if ($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}

if ($this->isMessageVideo()) {
if (!$user) {
return false;
}
if ($user->getId() === $this->getOwner()->getId()) {
return true;
}
return $this->isViewableByMessageParticipant($user);
}

if (get_class($this->getOwner()) == "openvk\\Web\\Models\\Entities\\User") {
return $this->getOwner()->canBeViewedBy($user) && $this->getOwner()->getPrivacyPermission('videos.read', $user);
} else {
Expand Down
2 changes: 2 additions & 0 deletions Web/Models/Repositories/Photos.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function getEveryUserPhoto(User $user, int $offset = 0, int $limit = 10):
"system" => 0,
"private" => 0,
"anonymous" => 0,
"is_message_photo" => 0,
])->order("id DESC");

foreach ($photos->limit($limit, $offset) as $photo) {
Expand All @@ -67,6 +68,7 @@ public function getUserPhotosCount(User $user)
"system" => 0,
"private" => 0,
"anonymous" => 0,
"is_message_photo" => 0,
]);

return sizeof($photos);
Expand Down
10 changes: 5 additions & 5 deletions Web/Models/Repositories/Videos.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,28 @@ public function getByOwnerAndVID(int $owner, int $vId): ?Video
public function getByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->page($page, $perPage)->order("created DESC") as $video) {
foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->page($page, $perPage)->order("created DESC") as $video) {
yield new Video($video);
}
}

public function getByUserLimit(User $user, int $offset = 0, int $limit = 10): \Traversable
{
$perPage ??= OPENVK_DEFAULT_PER_PAGE;
foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->limit($limit, $offset)->order("created DESC") as $video) {
foreach ($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->limit($limit, $offset)->order("created DESC") as $video) {
yield new Video($video);
}
}

public function getUserVideosCount(User $user): int
{
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0]));
return sizeof($this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0]));
}

public function find(string $query = "", array $params = [], array $order = ['type' => 'id', 'invert' => false]): Util\EntityStream
{
$query = "%$query%";
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0);
$result = $this->videos->where("CONCAT_WS(' ', name, description) LIKE ?", $query)->where("deleted", 0)->where("unlisted", 0)->where("is_message_video", 0);
$order_str = 'id';

switch ($order['type']) {
Expand Down Expand Up @@ -101,7 +101,7 @@ public function find(string $query = "", array $params = [], array $order = ['ty

public function getLastVideo(User $user)
{
$video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->order("id DESC")->fetch();
$video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0, "is_message_video" => 0])->order("id DESC")->fetch();

return new Video($video);
}
Expand Down
55 changes: 53 additions & 2 deletions Web/Presenters/MessengerPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ public function renderApiGetMessages(int $sel, int $lastMsg): void
$messages = [];
$correspondence = new Correspondence($this->user->identity, $correspondent);
foreach ($correspondence->getMessages(1, $lastMsg === 0 ? null : $lastMsg, null, 0) as $message) {
$messages[] = $message->simplify();
$simple = $message->simplify();
$this->enrichAttachmentsWithHTML($message, $simple);
$messages[] = $simple;
}

header("Content-Type: application/json");
Expand All @@ -166,13 +168,62 @@ public function renderApiWriteMessage(int $sel): void
exit();
}

$attachments = [];
if (!empty($this->postParam("attachments"))) {
$attachments_array = array_slice(explode(",", $this->postParam("attachments")), 0, OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["postSizes"]["maxAttachments"]);
if (sizeof($attachments_array) > 0) {
$attachments = parseAttachments($attachments_array, ['photo', 'video', 'audio', 'note', 'doc']);
}
}

$cor = new Correspondence($this->user->identity, $sel);
$msg = new Message();
$msg->setContent($this->postParam("content"));
$cor->sendMessage($msg);

foreach ($attachments as $attachment) {
if (!$attachment || $attachment->isDeleted() || !$attachment->canBeViewedBy($this->user->identity)) {
continue;
}

$msg->attach($attachment);
}

header("HTTP/1.1 202 Accepted");
header("Content-Type: application/json");
exit(json_encode($msg->simplify()));
$simple = $msg->simplify();
$this->enrichAttachmentsWithHTML($msg, $simple);
exit(json_encode($simple));
}

private function enrichAttachmentsWithHTML(Message $messageObj, array &$simplifiedArray): void
{
$children = iterator_to_array($messageObj->getChildren());

foreach ($simplifiedArray['attachments'] as $index => &$attachmentData) {
if (!isset($children[$index])) {
continue;
}

$originalObj = $children[$index];
$html = "";

if ($attachmentData['type'] === 'audio') {
$html = $this->getTemplatingEngine()->renderToString(
# костыль жоский
dirname(__FILE__) . '/templates/Audio/player.latte',
[
'audio' => $originalObj,
'thisUser' => $this->user->identity,
'hideButtons' => false,
'club' => null,
]
);
}

if ($html !== "") {
$attachmentData['html'] = $html;
}
}
}
}
Loading
Loading