@@ -519,7 +519,13 @@ protected function parseMessage(Message $chatMessage, $allowInaccurate): void {
519519 }
520520 } elseif ($ message === 'file_shared ' ) {
521521 try {
522- $ parsedParameters ['file ' ] = $ this ->getFileFromShare ($ room , $ participant , $ parameters ['share ' ], $ allowInaccurate );
522+ if (isset ($ parameters ['share ' ])) {
523+ $ parsedParameters ['file ' ] = $ this ->getFileFromShare ($ room , $ participant , $ parameters ['share ' ], $ allowInaccurate );
524+ } elseif (isset ($ parameters ['fileId ' ])) {
525+ $ parsedParameters ['file ' ] = $ this ->getFileFromNodeId ($ room , $ participant , (int )$ parameters ['fileId ' ], $ allowInaccurate );
526+ } else {
527+ throw new \InvalidArgumentException ('No share or fileId in file_shared message ' );
528+ }
523529 $ parsedMessage = '{file} ' ;
524530 $ metaData = $ parameters ['metaData ' ] ?? [];
525531 if (isset ($ metaData ['messageType ' ])) {
@@ -797,6 +803,94 @@ protected function parseDeletedMessage(Message $chatMessage): void {
797803 $ chatMessage ->setMessage ($ parsedMessage , $ parsedParameters , $ message );
798804 }
799805
806+ /**
807+ * Build the same file-metadata array as getFileFromShare() but starting
808+ * from a node ID rather than a share ID.
809+ *
810+ * Files posted via the conversation-folder mechanism are accessible to room
811+ * members through the folder-level TYPE_ROOM share, so
812+ * userFolder->getFirstNodeById() will find them via the share mount.
813+ *
814+ * @throws NotFoundException
815+ * @throws ShareNotFound
816+ */
817+ protected function getFileFromNodeId (Room $ room , ?Participant $ participant , int $ nodeId , bool $ allowInaccurate = false ): array {
818+ if ($ participant && $ participant ->getAttendee ()->getActorType () === Attendee::ACTOR_USERS ) {
819+ if ($ allowInaccurate ) {
820+ // Lightweight lookup: search the filecache directly without setting up the user
821+ // filesystem, mirroring getFileFromShare()'s use of getNodeCacheEntry().
822+ // Path is intentionally inaccurate (filename only) — callers that need the full
823+ // relative path must pass $allowInaccurate = false.
824+ $ node = $ this ->rootFolder ->getFirstNodeById ($ nodeId );
825+ if (!$ node instanceof Node) {
826+ throw new NotFoundException ('File node ' . $ nodeId . ' not found ' );
827+ }
828+
829+ $ name = $ node ->getName ();
830+ $ size = $ node ->getSize ();
831+ $ path = $ name ;
832+ } else {
833+ $ uid = $ participant ->getAttendee ()->getActorId ();
834+ $ userFolder = $ this ->rootFolder ->getUserFolder ($ uid );
835+
836+ $ node = $ userFolder ->getFirstNodeById ($ nodeId );
837+ if (!$ node instanceof Node) {
838+ throw new NotFoundException ('File node ' . $ nodeId . ' not found for user ' . $ uid );
839+ }
840+
841+ $ fullPath = $ node ->getPath ();
842+ $ pathSegments = explode ('/ ' , $ fullPath , 4 );
843+ $ name = $ node ->getName ();
844+ $ size = $ node ->getSize ();
845+ $ path = $ pathSegments [3 ] ?? $ name ;
846+ }
847+
848+ $ url = $ this ->url ->linkToRouteAbsolute ('files.viewcontroller.showFile ' , [
849+ 'fileid ' => $ node ->getId (),
850+ ]);
851+ } elseif ($ participant && $ room ->getType () !== Room::TYPE_PUBLIC && $ participant ->getAttendee ()->getActorType () === Attendee::ACTOR_FEDERATED_USERS ) {
852+ throw new ShareNotFound ();
853+ } else {
854+ // Guest / public room path: load via the owner's root folder.
855+ // Without a per-file share we cannot look up a share token, so
856+ // guests will see the file as unavailable.
857+ throw new ShareNotFound ();
858+ }
859+
860+ $ fileId = $ node ->getId ();
861+ $ isPreviewAvailable = $ size > 0 && $ this ->previewManager ->isMimeSupported ($ node ->getMimeType ());
862+
863+ $ data = [
864+ 'type ' => 'file ' ,
865+ 'id ' => (string )$ fileId ,
866+ 'name ' => $ name ,
867+ 'size ' => (string )$ size ,
868+ 'path ' => $ path ,
869+ 'link ' => $ url ,
870+ 'etag ' => $ node ->getEtag (),
871+ 'permissions ' => (string )$ node ->getPermissions (),
872+ 'mimetype ' => $ node ->getMimeType (),
873+ 'preview-available ' => $ isPreviewAvailable ? 'yes ' : 'no ' ,
874+ 'hide-download ' => 'no ' ,
875+ ];
876+
877+ if ($ isPreviewAvailable && str_starts_with ($ node ->getMimeType (), 'image/ ' )) {
878+ try {
879+ $ sizeMetadata = $ this ->metadataCache ->getImageMetadataForFileId ($ fileId );
880+ if (isset ($ sizeMetadata ['width ' ], $ sizeMetadata ['height ' ])) {
881+ $ data ['width ' ] = (string )$ sizeMetadata ['width ' ];
882+ $ data ['height ' ] = (string )$ sizeMetadata ['height ' ];
883+ }
884+ if (isset ($ sizeMetadata ['blurhash ' ])) {
885+ $ data ['blurhash ' ] = $ sizeMetadata ['blurhash ' ];
886+ }
887+ } catch (\OCP \FilesMetadata \Exceptions \FilesMetadataNotFoundException ) {
888+ }
889+ }
890+
891+ return $ data ;
892+ }
893+
800894 /**
801895 * @throws InvalidPathException
802896 * @throws NotFoundException
@@ -826,7 +920,9 @@ protected function getFileFromShare(Room $room, ?Participant $participant, strin
826920 // FIXME This should be much more sensible, e.g.
827921 // 1. Only be executed on "Waiting for new messages"
828922 // 2. Once per request
923+ /** @psalm-suppress UndefinedClass */
829924 \OC_Util::tearDownFS ();
925+ /** @psalm-suppress UndefinedClass */
830926 \OC_Util::setupFS ($ participant ->getAttendee ()->getActorId ());
831927 $ userNodes = $ userFolder ->getById ($ share ->getNodeId ());
832928
0 commit comments