From 40e28b7b3d5ee6722de10bce99d0c4be41a48ff0 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Fri, 3 Jul 2026 18:33:59 +0400 Subject: [PATCH 01/10] Add imbot and im methods --- .php-cs-fixer.php | 2 + CHANGELOG.md | 68 ++++ Makefile | 125 +++++++- phpstan.neon.dist | 1 + phpunit.xml.dist | 72 +++++ rector.php | 2 + src/Services/IM/Chat/ChatColor.php | 34 ++ src/Services/IM/Chat/ChatEntityType.php | 30 ++ src/Services/IM/Chat/ChatType.php | 21 ++ .../IM/Chat/Result/ChatItemResult.php | 25 ++ src/Services/IM/Chat/Result/ChatResult.php | 34 ++ .../IM/Chat/Result/ChatUserListResult.php | 32 ++ src/Services/IM/Chat/Service/Chat.php | 229 ++++++++++++++ src/Services/IM/Chat/Service/ChatUser.php | 86 +++++ .../IM/Counters/Result/CountersItemResult.php | 38 +++ .../IM/Counters/Result/CountersResult.php | 30 ++ src/Services/IM/Counters/Service/Counters.php | 40 +++ .../Result/DepartmentItemResult.php | 27 ++ .../DepartmentUsersByDepartmentResult.php | 62 ++++ .../Result/DepartmentUsersResult.php | 61 ++++ .../Department/Result/DepartmentsResult.php | 32 ++ .../IM/Department/Service/Department.php | 111 +++++++ .../IM/Dialog/Result/DialogActionResult.php | 28 ++ .../IM/Dialog/Result/DialogItemResult.php | 63 ++++ .../Result/DialogMessageSearchResult.php | 119 +++++++ .../IM/Dialog/Result/DialogMessagesResult.php | 73 +++++ .../IM/Dialog/Result/DialogReadResult.php | 35 +++ .../Result/DialogReadStateItemResult.php | 28 ++ .../IM/Dialog/Result/DialogResult.php | 37 +++ .../IM/Dialog/Result/DialogUserItemResult.php | 52 ++++ .../IM/Dialog/Result/DialogUsersResult.php | 48 +++ .../IM/Dialog/Result/MessageItemResult.php | 41 +++ src/Services/IM/Dialog/Service/Dialog.php | 212 +++++++++++++ .../IM/Disk/Result/FileCommitResult.php | 87 ++++++ .../IM/Disk/Result/FileDeleteResult.php | 28 ++ .../IM/Disk/Result/FileSaveResult.php | 73 +++++ .../IM/Disk/Result/FolderIdResult.php | 24 ++ .../IM/Disk/Result/RecordShareResult.php | 28 ++ src/Services/IM/Disk/Service/Disk.php | 149 +++++++++ .../IM/EventV2/Result/EventV2ItemResult.php | 29 ++ .../IM/EventV2/Result/EventsV2Result.php | 74 +++++ src/Services/IM/EventV2/Service/EventV2.php | 100 ++++++ .../IM/FileV2/Result/FileV2DownloadResult.php | 37 +++ .../IM/FileV2/Result/FileV2ItemResult.php | 29 ++ .../IM/FileV2/Result/FileV2UploadResult.php | 57 ++++ src/Services/IM/FileV2/Service/FileV2.php | 87 ++++++ src/Services/IM/IMServiceBuilder.php | 161 +++++++++- src/Services/IM/Message/Attach/Attach.php | 121 ++++++++ .../Message/Attach/Blocks/DelimiterBlock.php | 69 +++++ .../IM/Message/Attach/Blocks/FileBlock.php | 50 +++ .../IM/Message/Attach/Blocks/GridBlock.php | 137 ++++++++ .../IM/Message/Attach/Blocks/ImageBlock.php | 50 +++ .../IM/Message/Attach/Blocks/LinkBlock.php | 238 ++++++++++++++ .../IM/Message/Attach/Blocks/MessageBlock.php | 40 +++ .../IM/Message/Attach/Blocks/UserBlock.php | 149 +++++++++ .../Attach/Contracts/AttachBlockInterface.php | 19 ++ .../Attach/Contracts/AttachItemInterface.php | 19 ++ .../Contracts/AttachPayloadInterface.php | 19 ++ .../Message/Attach/Enums/AttachAvatarType.php | 21 ++ .../Message/Attach/Enums/AttachColorToken.php | 22 ++ .../IM/Message/Attach/Enums/GridDisplay.php | 22 ++ .../IM/Message/Attach/Items/FileItem.php | 75 +++++ .../IM/Message/Attach/Items/GridItem.php | 157 ++++++++++ .../IM/Message/Attach/Items/ImageItem.php | 99 ++++++ src/Services/IM/Message/Attach/RawAttach.php | 41 +++ .../IM/Message/Service/LikeAction.php | 21 ++ src/Services/IM/Message/Service/Message.php | 202 ++++++++++++ src/Services/IM/Message/Service/ShareType.php | 22 ++ .../IM/Notify/Result/NotifiesResult.php | 76 +++++ .../Result/NotifyHistorySearchResult.php | 68 ++++ .../IM/Notify/Result/NotifyItemResult.php | 37 +++ .../IM/Notify/Result/NotifyReadAllResult.php | 36 +++ .../Notify/Result/NotifySchemaItemResult.php | 25 ++ .../IM/Notify/Result/NotifySchemaResult.php | 34 ++ src/Services/IM/Notify/Service/Notify.php | 213 +++++++++++-- .../Placements/AbstractPlacementOptions.php | 54 ++++ src/Services/IM/Placements/ChatContext.php | 31 ++ .../ImContextMenuPlacementOptions.php | 24 ++ .../ImNavigationPlacementOptions.php | 23 ++ .../Placements/ImSidebarPlacementOptions.php | 34 ++ .../Placements/ImTextareaPlacementOptions.php | 49 +++ src/Services/IM/Placements/PlacementColor.php | 38 +++ .../IM/Placements/PlacementLangItem.php | 44 +++ .../IM/Placements/PlacementLangMap.php | 53 ++++ .../IM/Placements/PlacementLocationCodes.php | 47 +++ src/Services/IM/Placements/Placements.php | 204 ++++++++++++ .../IM/Recent/Result/RecentItemResult.php | 39 +++ .../IM/Recent/Result/RecentsResult.php | 32 ++ src/Services/IM/Recent/Service/Recent.php | 121 ++++++++ .../IM/Revision/Result/RevisionItemResult.php | 29 ++ .../IM/Revision/Result/RevisionResult.php | 28 ++ src/Services/IM/Revision/Service/Revision.php | 40 +++ .../IM/Search/Result/SearchChatItemResult.php | 57 ++++ .../IM/Search/Result/SearchChatsResult.php | 48 +++ .../Result/SearchDepartmentItemResult.php | 27 ++ .../Search/Result/SearchDepartmentsResult.php | 48 +++ .../Search/Result/SearchLastItemsResult.php | 29 ++ .../IM/Search/Result/SearchUserItemResult.php | 43 +++ .../IM/Search/Result/SearchUsersResult.php | 40 +++ src/Services/IM/Search/Service/Search.php | 171 ++++++++++ .../IM/User/Result/UserItemResult.php | 51 +++ src/Services/IM/User/Result/UserResult.php | 37 +++ .../IM/User/Result/UserStatusItemResult.php | 24 ++ .../IM/User/Result/UserStatusResult.php | 30 ++ src/Services/IM/User/Result/UsersResult.php | 32 ++ src/Services/IM/User/Service/User.php | 64 ++++ src/Services/IM/User/Service/UserStatus.php | 86 +++++ src/Services/IM/User/UserStatusType.php | 24 ++ src/Services/IMBot/Bot/BotBackground.php | 32 ++ src/Services/IMBot/Bot/BotEventMode.php | 28 ++ src/Services/IMBot/Bot/BotType.php | 28 ++ .../IMBot/Bot/Result/BotItemResult.php | 38 +++ src/Services/IMBot/Bot/Result/BotResult.php | 43 +++ src/Services/IMBot/Bot/Result/BotsResult.php | 56 ++++ src/Services/IMBot/Bot/Service/Bot.php | 215 +++++++++++++ src/Services/IMBot/Chat/ChatColor.php | 39 +++ .../IMBot/Chat/Result/ChatItemResult.php | 55 ++++ .../IMBot/Chat/Result/ChatLeaveResult.php | 33 ++ src/Services/IMBot/Chat/Result/ChatResult.php | 43 +++ .../IMBot/Chat/Result/ChatUserListResult.php | 35 +++ src/Services/IMBot/Chat/Service/Chat.php | 201 ++++++++++++ .../IMBot/Chat/Service/ChatInputAction.php | 56 ++++ .../IMBot/Chat/Service/ChatManager.php | 90 ++++++ .../IMBot/Chat/Service/ChatTextField.php | 58 ++++ src/Services/IMBot/Chat/Service/ChatUser.php | 123 ++++++++ .../Result/ChatMessageItemResult.php | 33 ++ .../ChatMessage/Result/ChatMessageResult.php | 45 +++ .../Result/ChatMessageSentBatchResult.php | 43 +++ .../Result/ChatMessageSentResult.php | 43 +++ .../IMBot/ChatMessage/Service/Batch.php | 123 ++++++++ .../IMBot/ChatMessage/Service/ChatMessage.php | 293 ++++++++++++++++++ .../Service/ChatMessageReaction.php | 90 ++++++ .../Command/Result/CommandItemResult.php | 30 ++ .../IMBot/Command/Result/CommandResult.php | 35 +++ .../IMBot/Command/Result/CommandsResult.php | 38 +++ .../IMBot/Command/Service/Command.php | 231 ++++++++++++++ .../IMBot/Event/Result/EventItemResult.php | 29 ++ .../IMBot/Event/Result/EventsResult.php | 74 +++++ src/Services/IMBot/Event/Service/Event.php | 77 +++++ .../IMBot/File/Result/FileDownloadResult.php | 37 +++ .../IMBot/File/Result/FileItemResult.php | 29 ++ .../IMBot/File/Result/FileUploadResult.php | 57 ++++ src/Services/IMBot/File/Service/File.php | 105 +++++++ src/Services/IMBot/IMBotServiceBuilder.php | 196 ++++++++++++ .../Revision/Result/RevisionItemResult.php | 28 ++ .../IMBot/Revision/Result/RevisionResult.php | 31 ++ .../IMBot/Revision/Service/Revision.php | 44 +++ src/Services/ServiceBuilder.php | 15 + .../IM/Chat/Result/ChatItemResultTest.php | 109 +++++++ .../Services/IM/Chat/Service/ChatTest.php | 269 ++++++++++++++++ .../Services/IM/Chat/Service/ChatUserTest.php | 173 +++++++++++ .../Result/CountersItemResultTest.php | 74 +++++ .../IM/Counters/Service/CountersTest.php | 54 ++++ .../DepartmentItemResultAnnotationsTest.php | 87 ++++++ .../IM/Department/Service/DepartmentTest.php | 113 +++++++ .../DialogItemResultAnnotationsTest.php | 71 +++++ .../DialogUserItemResultAnnotationsTest.php | 72 +++++ .../MessageItemResultAnnotationsTest.php | 89 ++++++ .../IM/Dialog/Service/DialogChatTestCase.php | 118 +++++++ .../Services/IM/Dialog/Service/DialogTest.php | 193 ++++++++++++ .../Services/IM/Disk/Service/DiskTest.php | 177 +++++++++++ .../IM/EventV2/Service/EventV2Test.php | 84 +++++ .../Services/IM/FileV2/Service/FileV2Test.php | 132 ++++++++ .../MessageAttachDelimiterBlockTest.php | 90 ++++++ .../Service/MessageAttachFileBlockTest.php | 100 ++++++ .../Service/MessageAttachFormatTest.php | 66 ++++ .../Service/MessageAttachGridBlockTest.php | 192 ++++++++++++ .../Service/MessageAttachImageBlockTest.php | 103 ++++++ .../Service/MessageAttachLinkBlockTest.php | 123 ++++++++ .../Service/MessageAttachMessageBlockTest.php | 71 +++++ .../Service/MessageAttachObjectApiTest.php | 62 ++++ .../Service/MessageAttachUserBlockTest.php | 105 +++++++ .../Service/MessageChatPayloadTest.php | 40 +++ .../Message/Service/MessageChatTestCase.php | 158 ++++++++++ .../Message/Service/MessageFormattingTest.php | 69 +++++ .../Message/Service/MessageKeyboardTest.php | 56 ++++ .../IM/Message/Service/MessageMenuTest.php | 53 ++++ .../IM/Message/Service/MessageTest.php | 130 ++++++++ .../NotifyItemResultAnnotationsTest.php | 85 +++++ .../NotifySchemaItemResultAnnotationsTest.php | 77 +++++ .../Services/IM/Notify/Service/NotifyTest.php | 226 ++++++++++++++ .../Placements/PlacementLocationCodesTest.php | 65 ++++ .../Services/IM/Placements/PlacementsTest.php | 111 +++++++ .../RecentItemResultAnnotationsTest.php | 87 ++++++ .../Services/IM/Recent/Service/RecentTest.php | 124 ++++++++ .../Result/RevisionItemResultTest.php | 72 +++++ .../IM/Revision/Service/RevisionTest.php | 52 ++++ .../SearchChatItemResultAnnotationsTest.php | 87 ++++++ ...rchDepartmentItemResultAnnotationsTest.php | 87 ++++++ .../SearchUserItemResultAnnotationsTest.php | 87 ++++++ .../Services/IM/Search/Service/SearchTest.php | 123 ++++++++ .../Services/IM/Service/NotifyTest.php | 7 +- .../IM/User/Result/UserItemResultTest.php | 73 +++++ .../IM/User/Service/UserStatusTest.php | 87 ++++++ .../Services/IM/User/Service/UserTest.php | 88 ++++++ .../Services/IMBot/Bot/Service/BotTest.php | 146 +++++++++ .../Services/IMBot/Chat/Service/ChatTest.php | 138 +++++++++ .../IMBot/ChatMessage/Service/BatchTest.php | 225 ++++++++++++++ .../IMBot/Command/Service/CommandTest.php | 152 +++++++++ .../Services/IMBot/File/Service/FileTest.php | 151 +++++++++ .../IMBot/Revision/Service/RevisionTest.php | 52 ++++ 201 files changed, 15138 insertions(+), 40 deletions(-) create mode 100644 src/Services/IM/Chat/ChatColor.php create mode 100644 src/Services/IM/Chat/ChatEntityType.php create mode 100644 src/Services/IM/Chat/ChatType.php create mode 100644 src/Services/IM/Chat/Result/ChatItemResult.php create mode 100644 src/Services/IM/Chat/Result/ChatResult.php create mode 100644 src/Services/IM/Chat/Result/ChatUserListResult.php create mode 100644 src/Services/IM/Chat/Service/Chat.php create mode 100644 src/Services/IM/Chat/Service/ChatUser.php create mode 100644 src/Services/IM/Counters/Result/CountersItemResult.php create mode 100644 src/Services/IM/Counters/Result/CountersResult.php create mode 100644 src/Services/IM/Counters/Service/Counters.php create mode 100644 src/Services/IM/Department/Result/DepartmentItemResult.php create mode 100644 src/Services/IM/Department/Result/DepartmentUsersByDepartmentResult.php create mode 100644 src/Services/IM/Department/Result/DepartmentUsersResult.php create mode 100644 src/Services/IM/Department/Result/DepartmentsResult.php create mode 100644 src/Services/IM/Department/Service/Department.php create mode 100644 src/Services/IM/Dialog/Result/DialogActionResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogItemResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogMessageSearchResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogMessagesResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogReadResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogReadStateItemResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogUserItemResult.php create mode 100644 src/Services/IM/Dialog/Result/DialogUsersResult.php create mode 100644 src/Services/IM/Dialog/Result/MessageItemResult.php create mode 100644 src/Services/IM/Dialog/Service/Dialog.php create mode 100644 src/Services/IM/Disk/Result/FileCommitResult.php create mode 100644 src/Services/IM/Disk/Result/FileDeleteResult.php create mode 100644 src/Services/IM/Disk/Result/FileSaveResult.php create mode 100644 src/Services/IM/Disk/Result/FolderIdResult.php create mode 100644 src/Services/IM/Disk/Result/RecordShareResult.php create mode 100644 src/Services/IM/Disk/Service/Disk.php create mode 100644 src/Services/IM/EventV2/Result/EventV2ItemResult.php create mode 100644 src/Services/IM/EventV2/Result/EventsV2Result.php create mode 100644 src/Services/IM/EventV2/Service/EventV2.php create mode 100644 src/Services/IM/FileV2/Result/FileV2DownloadResult.php create mode 100644 src/Services/IM/FileV2/Result/FileV2ItemResult.php create mode 100644 src/Services/IM/FileV2/Result/FileV2UploadResult.php create mode 100644 src/Services/IM/FileV2/Service/FileV2.php create mode 100644 src/Services/IM/Message/Attach/Attach.php create mode 100644 src/Services/IM/Message/Attach/Blocks/DelimiterBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/FileBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/GridBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/ImageBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/LinkBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/MessageBlock.php create mode 100644 src/Services/IM/Message/Attach/Blocks/UserBlock.php create mode 100644 src/Services/IM/Message/Attach/Contracts/AttachBlockInterface.php create mode 100644 src/Services/IM/Message/Attach/Contracts/AttachItemInterface.php create mode 100644 src/Services/IM/Message/Attach/Contracts/AttachPayloadInterface.php create mode 100644 src/Services/IM/Message/Attach/Enums/AttachAvatarType.php create mode 100644 src/Services/IM/Message/Attach/Enums/AttachColorToken.php create mode 100644 src/Services/IM/Message/Attach/Enums/GridDisplay.php create mode 100644 src/Services/IM/Message/Attach/Items/FileItem.php create mode 100644 src/Services/IM/Message/Attach/Items/GridItem.php create mode 100644 src/Services/IM/Message/Attach/Items/ImageItem.php create mode 100644 src/Services/IM/Message/Attach/RawAttach.php create mode 100644 src/Services/IM/Message/Service/LikeAction.php create mode 100644 src/Services/IM/Message/Service/Message.php create mode 100644 src/Services/IM/Message/Service/ShareType.php create mode 100644 src/Services/IM/Notify/Result/NotifiesResult.php create mode 100644 src/Services/IM/Notify/Result/NotifyHistorySearchResult.php create mode 100644 src/Services/IM/Notify/Result/NotifyItemResult.php create mode 100644 src/Services/IM/Notify/Result/NotifyReadAllResult.php create mode 100644 src/Services/IM/Notify/Result/NotifySchemaItemResult.php create mode 100644 src/Services/IM/Notify/Result/NotifySchemaResult.php create mode 100644 src/Services/IM/Placements/AbstractPlacementOptions.php create mode 100644 src/Services/IM/Placements/ChatContext.php create mode 100644 src/Services/IM/Placements/ImContextMenuPlacementOptions.php create mode 100644 src/Services/IM/Placements/ImNavigationPlacementOptions.php create mode 100644 src/Services/IM/Placements/ImSidebarPlacementOptions.php create mode 100644 src/Services/IM/Placements/ImTextareaPlacementOptions.php create mode 100644 src/Services/IM/Placements/PlacementColor.php create mode 100644 src/Services/IM/Placements/PlacementLangItem.php create mode 100644 src/Services/IM/Placements/PlacementLangMap.php create mode 100644 src/Services/IM/Placements/PlacementLocationCodes.php create mode 100644 src/Services/IM/Placements/Placements.php create mode 100644 src/Services/IM/Recent/Result/RecentItemResult.php create mode 100644 src/Services/IM/Recent/Result/RecentsResult.php create mode 100644 src/Services/IM/Recent/Service/Recent.php create mode 100644 src/Services/IM/Revision/Result/RevisionItemResult.php create mode 100644 src/Services/IM/Revision/Result/RevisionResult.php create mode 100644 src/Services/IM/Revision/Service/Revision.php create mode 100644 src/Services/IM/Search/Result/SearchChatItemResult.php create mode 100644 src/Services/IM/Search/Result/SearchChatsResult.php create mode 100644 src/Services/IM/Search/Result/SearchDepartmentItemResult.php create mode 100644 src/Services/IM/Search/Result/SearchDepartmentsResult.php create mode 100644 src/Services/IM/Search/Result/SearchLastItemsResult.php create mode 100644 src/Services/IM/Search/Result/SearchUserItemResult.php create mode 100644 src/Services/IM/Search/Result/SearchUsersResult.php create mode 100644 src/Services/IM/Search/Service/Search.php create mode 100644 src/Services/IM/User/Result/UserItemResult.php create mode 100644 src/Services/IM/User/Result/UserResult.php create mode 100644 src/Services/IM/User/Result/UserStatusItemResult.php create mode 100644 src/Services/IM/User/Result/UserStatusResult.php create mode 100644 src/Services/IM/User/Result/UsersResult.php create mode 100644 src/Services/IM/User/Service/User.php create mode 100644 src/Services/IM/User/Service/UserStatus.php create mode 100644 src/Services/IM/User/UserStatusType.php create mode 100644 src/Services/IMBot/Bot/BotBackground.php create mode 100644 src/Services/IMBot/Bot/BotEventMode.php create mode 100644 src/Services/IMBot/Bot/BotType.php create mode 100644 src/Services/IMBot/Bot/Result/BotItemResult.php create mode 100644 src/Services/IMBot/Bot/Result/BotResult.php create mode 100644 src/Services/IMBot/Bot/Result/BotsResult.php create mode 100644 src/Services/IMBot/Bot/Service/Bot.php create mode 100644 src/Services/IMBot/Chat/ChatColor.php create mode 100644 src/Services/IMBot/Chat/Result/ChatItemResult.php create mode 100644 src/Services/IMBot/Chat/Result/ChatLeaveResult.php create mode 100644 src/Services/IMBot/Chat/Result/ChatResult.php create mode 100644 src/Services/IMBot/Chat/Result/ChatUserListResult.php create mode 100644 src/Services/IMBot/Chat/Service/Chat.php create mode 100644 src/Services/IMBot/Chat/Service/ChatInputAction.php create mode 100644 src/Services/IMBot/Chat/Service/ChatManager.php create mode 100644 src/Services/IMBot/Chat/Service/ChatTextField.php create mode 100644 src/Services/IMBot/Chat/Service/ChatUser.php create mode 100644 src/Services/IMBot/ChatMessage/Result/ChatMessageItemResult.php create mode 100644 src/Services/IMBot/ChatMessage/Result/ChatMessageResult.php create mode 100644 src/Services/IMBot/ChatMessage/Result/ChatMessageSentBatchResult.php create mode 100644 src/Services/IMBot/ChatMessage/Result/ChatMessageSentResult.php create mode 100644 src/Services/IMBot/ChatMessage/Service/Batch.php create mode 100644 src/Services/IMBot/ChatMessage/Service/ChatMessage.php create mode 100644 src/Services/IMBot/ChatMessage/Service/ChatMessageReaction.php create mode 100644 src/Services/IMBot/Command/Result/CommandItemResult.php create mode 100644 src/Services/IMBot/Command/Result/CommandResult.php create mode 100644 src/Services/IMBot/Command/Result/CommandsResult.php create mode 100644 src/Services/IMBot/Command/Service/Command.php create mode 100644 src/Services/IMBot/Event/Result/EventItemResult.php create mode 100644 src/Services/IMBot/Event/Result/EventsResult.php create mode 100644 src/Services/IMBot/Event/Service/Event.php create mode 100644 src/Services/IMBot/File/Result/FileDownloadResult.php create mode 100644 src/Services/IMBot/File/Result/FileItemResult.php create mode 100644 src/Services/IMBot/File/Result/FileUploadResult.php create mode 100644 src/Services/IMBot/File/Service/File.php create mode 100644 src/Services/IMBot/IMBotServiceBuilder.php create mode 100644 src/Services/IMBot/Revision/Result/RevisionItemResult.php create mode 100644 src/Services/IMBot/Revision/Result/RevisionResult.php create mode 100644 src/Services/IMBot/Revision/Service/Revision.php create mode 100644 tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php create mode 100644 tests/Integration/Services/IM/Chat/Service/ChatTest.php create mode 100644 tests/Integration/Services/IM/Chat/Service/ChatUserTest.php create mode 100644 tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php create mode 100644 tests/Integration/Services/IM/Counters/Service/CountersTest.php create mode 100644 tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Department/Service/DepartmentTest.php create mode 100644 tests/Integration/Services/IM/Dialog/Result/DialogItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Dialog/Result/DialogUserItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Dialog/Result/MessageItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php create mode 100644 tests/Integration/Services/IM/Dialog/Service/DialogTest.php create mode 100644 tests/Integration/Services/IM/Disk/Service/DiskTest.php create mode 100644 tests/Integration/Services/IM/EventV2/Service/EventV2Test.php create mode 100644 tests/Integration/Services/IM/FileV2/Service/FileV2Test.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachDelimiterBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachFileBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachFormatTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachGridBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachImageBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachLinkBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachMessageBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachObjectApiTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageAttachUserBlockTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageChatPayloadTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageFormattingTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageKeyboardTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageMenuTest.php create mode 100644 tests/Integration/Services/IM/Message/Service/MessageTest.php create mode 100644 tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Notify/Service/NotifyTest.php create mode 100644 tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php create mode 100644 tests/Integration/Services/IM/Placements/PlacementsTest.php create mode 100644 tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Recent/Service/RecentTest.php create mode 100644 tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php create mode 100644 tests/Integration/Services/IM/Revision/Service/RevisionTest.php create mode 100644 tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php create mode 100644 tests/Integration/Services/IM/Search/Service/SearchTest.php create mode 100644 tests/Integration/Services/IM/User/Result/UserItemResultTest.php create mode 100644 tests/Integration/Services/IM/User/Service/UserStatusTest.php create mode 100644 tests/Integration/Services/IM/User/Service/UserTest.php create mode 100644 tests/Integration/Services/IMBot/Bot/Service/BotTest.php create mode 100644 tests/Integration/Services/IMBot/Chat/Service/ChatTest.php create mode 100644 tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php create mode 100644 tests/Integration/Services/IMBot/Command/Service/CommandTest.php create mode 100644 tests/Integration/Services/IMBot/File/Service/FileTest.php create mode 100644 tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 6e5393c3..836844e1 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -29,6 +29,8 @@ ->in(__DIR__ . '/src/Services/SonetGroup/') ->in(__DIR__ . '/src/Services/IMOpenLines/') ->in(__DIR__ . '/src/Services/Landing/') + ->in(__DIR__ . '/src/Services/IM/') + ->in(__DIR__ . '/src/Services/IMBot/') ->name('*.php') ->exclude(['vendor', 'storage', 'docker', 'docs']) // Exclude directories ->ignoreDotFiles(true) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4bc3b52..7bc7405c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,74 @@ ### Added +- Added service `Services\IMBot` scope with support for `imbot.v2.*` methods, + see [imbot.v2.* methods](https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/index.html) ([#505](https://github.com/bitrix24/b24phpsdk/issues/505)): + - `Bot::register` registers a new chat-bot (`imbot.v2.Bot.register`) + - `Bot::update` updates an existing chat-bot (`imbot.v2.Bot.update`) + - `Bot::get` gets information about a chat-bot (`imbot.v2.Bot.get`) + - `Bot::list` gets the list of chat-bots for the current application (`imbot.v2.Bot.list`) + - `Bot::unregister` unregisters a chat-bot (`imbot.v2.Bot.unregister`) + - `Chat::add` creates a new group chat on behalf of the bot (`imbot.v2.Chat.add`) + - `Chat::get` gets chat information by chat ID (`imbot.v2.Chat.get`) + - `Chat::update` updates chat properties (`imbot.v2.Chat.update`) + - `Chat::leave` removes the bot from a chat (`imbot.v2.Chat.leave`) + - `Chat::setOwner` transfers chat ownership to another user (`imbot.v2.Chat.setOwner`) + - `ChatUser::add` adds users to a chat (`imbot.v2.Chat.User.add`) + - `ChatUser::delete` removes a user from a chat (`imbot.v2.Chat.User.delete`) + - `ChatUser::list` gets the list of users in a chat (`imbot.v2.Chat.User.list`) + - `ChatManager::add` assigns manager rights to a chat member (`imbot.v2.Chat.Manager.add`) + - `ChatManager::delete` revokes manager rights from a chat member (`imbot.v2.Chat.Manager.delete`) + - `ChatMessage::send` sends a message on behalf of the bot (`imbot.v2.Chat.Message.send`), with batch calls support + - `ChatMessage::update` updates a previously sent message (`imbot.v2.Chat.Message.update`), with batch calls support + - `ChatMessage::delete` deletes a message sent by the bot (`imbot.v2.Chat.Message.delete`), with batch calls support + - `ChatMessage::read` marks a message as read (`imbot.v2.Chat.Message.read`) + - `ChatMessage::get` gets a message by its ID (`imbot.v2.Chat.Message.get`) + - `ChatMessage::getContext` gets message context around a given message (`imbot.v2.Chat.Message.getContext`) + - `ChatMessageReaction::add` adds a reaction to a message (`imbot.v2.Chat.Message.Reaction.add`) + - `ChatMessageReaction::delete` removes a reaction from a message (`imbot.v2.Chat.Message.Reaction.delete`) + - `Command::register` registers a slash command for a bot (`imbot.v2.Command.register`) + - `Command::update` updates an existing slash command (`imbot.v2.Command.update`) + - `Command::list` gets the list of commands registered for a bot (`imbot.v2.Command.list`) + - `Command::unregister` unregisters a slash command (`imbot.v2.Command.unregister`) + - `Command::answer` answers a command invocation with a message (`imbot.v2.Command.answer`) + - `ChatInputAction::notify` sends a typing indicator in the chat (`imbot.v2.Chat.InputAction.notify`) + - `ChatTextField::enabled` enables or disables the text field in a chat (`imbot.v2.Chat.TextField.enabled`) + - `Event::get` polls pending events for the bot in fetch mode (`imbot.v2.Event.get`), returns typed `EventsResult` with `EventItemResult` items, `getNextOffset()`, and `isHasMore()` + - `File::upload` uploads a file to a chat on behalf of the bot (`imbot.v2.File.upload`) + - `File::download` gets a download URL for a file in a chat (`imbot.v2.File.download`), returns typed `FileDownloadResult` + - `Revision::get` gets REST API and client protocol revision numbers (`imbot.v2.Revision.get`) +- Added services `Services\IM\EventV2` and `Services\IM\FileV2` with support for `im.v2.*` methods, + see [im.v2.* methods](https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/) ([#505](https://github.com/bitrix24/b24phpsdk/issues/505)): + - `EventV2::subscribe` subscribes the current user to message event recording (`im.v2.Event.subscribe`) + - `EventV2::unsubscribe` unsubscribes the current user from message event recording (`im.v2.Event.unsubscribe`) + - `EventV2::get` polls pending message events for the current user (`im.v2.Event.get`), returns typed `EventsV2Result` with `EventV2ItemResult` items, `getNextOffset()`, and `isHasMore()` + - `FileV2::upload` uploads a file to a chat (`im.v2.File.upload`) + - `FileV2::download` gets a download URL for a file in a chat (`im.v2.File.download`), returns typed `FileV2DownloadResult` + +- Added `Bitrix24\SDK\Services\IM\Department\Service\Department` service wrapping `im.department.get`, `im.department.colleagues.list`, `im.department.employees.get`, and `im.department.managers.get`, with typed department/user result wrappers and `IMServiceBuilder::department()` accessor +- Added `Bitrix24\SDK\Services\IM\Disk\Service\Disk` with `getFolderId(?int $chatId = null, ?string $dialogId = null)` for `im.disk.folder.get`, plus dedicated `FolderIdResult`, IM builder registration, and focused unit/integration coverage +- Added IM Disk file operations to `Bitrix24\SDK\Services\IM\Disk\Service\Disk`: `commitFile`, `deleteFile`, `saveFile`, and `shareRecord`, with dedicated result wrappers and live IM Disk integration coverage +- Added `Bitrix24\SDK\Services\IM\Chat\Service\ChatUser` service wrapping `im.chat.user.add`, `im.chat.user.delete`, `im.chat.user.list` for chat participant management, with `ChatUserListResult` and `IMServiceBuilder::chatUser()` +- Added `Bitrix24\SDK\Services\IM\Search\Service\Search` service wrapping `im.search.chat.list`, `im.search.user.list`, `im.search.department.list`, and deprecated legacy `im.search.last.*` methods, with typed search result wrappers and `IMServiceBuilder::search()` accessor +- Added `Bitrix24\SDK\Services\IM\Recent\Service\Recent` service wrapping `im.recent.get`, `im.recent.list`, `im.recent.pin`, `im.recent.unread`, and `im.recent.hide`, with `RecentItemResult`/`RecentsResult` and `IMServiceBuilder::recent()` accessor +- Added `Bitrix24\SDK\Services\IM\Revision\Service\Revision` service wrapping `im.revision.get` for IM module API revision/compatibility checks, with `RevisionItemResult` (`rest`, `web`, `mobile`, `desktop`, `im_revision_mobile` fields) and `IMServiceBuilder::revision()` accessor +- Added `IM\Counters` service with `im.counters.get` support for retrieving unread message and notification counters +- Added typed fluent `Services\IM\Message\Attach` payload builders for `ATTACH` blocks in `im.message.add` and `im.message.update`, plus `RawAttach::fromArray()` as an object-based escape hatch for unsupported or vendor-extended payload shapes +- Added `Bitrix24\SDK\Services\IM\Chat\Service\Chat` service wrapping `im.chat.add`, `im.chat.get`, `im.chat.leave`, `im.chat.mute`, `im.chat.setOwner`, `im.chat.updateAvatar`, `im.chat.updateColor`, `im.chat.updateTitle`, with enums `ChatType`, `ChatColor`, `ChatEntityType`, result types `ChatItemResult`/`ChatResult`, and `IMServiceBuilder::chat()` accessor +- Added `Bitrix24\SDK\Services\IM\Dialog\Service\Dialog` service for `im.dialog.*` support, with typed result wrappers, `IMServiceBuilder::dialog()`, and dedicated unit/integration/annotation coverage +- Added `Bitrix24\SDK\Services\IM\User\Service\User` service for `im.user.get` and `im.user.list.get` support, with typed result wrappers `UserResult`/`UsersResult`/`UserItemResult`, `IMServiceBuilder::user()` accessor, and unit/integration/annotation test coverage +- Added `b24-dev:result-item-generator` developer command for staged ResultItem payload build, verification, apply, and generation workflows backed by OpenAPI, REST documentation, and live API metadata +- Added `Services\IM\Message\Service\Message` service for `im.message.*` support: + - `add` — send a message (`im.message.add`) + - `update` — edit text and parameters (`im.message.update`) + - `delete` — delete a message (`im.message.delete`) + - `like` — toggle the Like mark (`im.message.like`), with typed `LikeAction` enum (`auto`/`plus`/`minus`) + - `share` — create an object from a message (`im.message.share`), with typed `ShareType` enum (`CHAT`/`TASK`/`POST`/`CALEND`) + - `command` — invoke a chat-bot command (`im.message.command`) +- Added `IMServiceBuilder::message()` accessor and cached service instance +- Added `Bitrix24\SDK\Services\IM\User\Service\UserStatus` service wrapping `im.user.status.get`, `im.user.status.set`, `im.user.status.idle.start`, and `im.user.status.idle.end`, with `UserStatusType` enum and `UserStatusResult`; exposed via `IMServiceBuilder::userStatus()` +- Extended `IM\Notify` service with `send` (`im.notify`), `getList` (`im.notify.get`), `historySearch` (`im.notify.history.search`), `markAllAsRead` (`im.notify.read.all`), `getSchema` (`im.notify.schema.get`) methods; refactored `markMessagesAsRead`/`markMessagesAsUnread` to call `im.notify.read.list` instead of `im.notify.read`; added `NotifyItemResult`, `NotifiesResult`, `NotifyHistorySearchResult`, `NotifyReadAllResult`, `NotifySchemaItemResult`, `NotifySchemaResult` result types + - Added service `Services\Landing\Site\Service\Site` with support methods, see [landing.site.* methods](https://github.com/bitrix24/b24phpsdk/issues/267): - `add` adds a site diff --git a/Makefile b/Makefile index c2fc4a9a..9c8f06b2 100644 --- a/Makefile +++ b/Makefile @@ -69,7 +69,30 @@ help: @echo "test-integration-landing-role - run Landing Role integration tests" @echo "test-integration-landing-repowidget - run Landing RepoWidget integration tests" @echo "test-integration-scope-landing-template - run Landing Template integration tests" - + @echo "test-integration-scope-imbot - run IMBot scope integration tests" + @echo "test-integration-imbot-bot - run IMBot Bot integration tests" + @echo "test-integration-imbot-chat - run IMBot Chat integration tests" + @echo "test-integration-imbot-command - run IMBot Command integration tests" + @echo "test-integration-imbot-revision - run IMBot Revision integration tests" + @echo "test-integration-imbot-chat-message - run IMBot ChatMessage integration tests (incl. batch)" + @echo "test-integration-imbot-event - run IMBot Event integration tests" + @echo "test-integration-imbot-file - run IMBot File integration tests" + @echo "test-integration-scope-im - run IM scope integration tests" + @echo "test-integration-im-disk - run IM Disk integration tests" + @echo "test-integration-im-message - run IM Message integration tests" + @echo "test-integration-im-dialog - run IM Dialog integration tests" + @echo "test-integration-im-department - run IM Department integration tests" + @echo "test-integration-im-user - run IM User integration tests" + @echo "test-integration-im-revision - run IM Revision integration tests" + @echo "test-integration-im-counters - run IM Counters integration tests" + @echo "test-integration-im-recent - run IM Recent integration tests" + @echo "test-integration-im-search - run IM Search integration tests" + @echo "test-integration-im-user-status - run IM UserStatus integration tests" + @echo "test-integration-im-chat - run IM Chat integration tests" + @echo "test-integration-im-chat-user - run IM Chat User integration tests" + @echo "test-integration-im-notify - run IM Notify integration tests" + @echo "test-integration-im-event-v2 - run IM EventV2 integration tests" + @echo "test-integration-im-file-v2 - run IM FileV2 integration tests" .PHONY: docker-init docker-init: @@ -170,10 +193,6 @@ test-integration-scope-telephony: test-integration-scope-workflows: docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_workflows -.PHONY: test-integration-scope-im -test-integration-scope-im: - docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_im - .PHONY: test-integration-scope-placement test-integration-scope-placement: docker-compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_placement @@ -492,6 +511,102 @@ test-integration-landing-role: test-integration-landing-repowidget: docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_landing_repowidget +.PHONY: test-integration-scope-imbot +test-integration-scope-imbot: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_imbot + +.PHONY: test-integration-imbot-bot +test-integration-imbot-bot: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_bot + +.PHONY: test-integration-imbot-chat +test-integration-imbot-chat: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_chat + +.PHONY: test-integration-imbot-command +test-integration-imbot-command: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_command + +.PHONY: test-integration-imbot-revision +test-integration-imbot-revision: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_revision + +.PHONY: test-integration-imbot-chat-message +test-integration-imbot-chat-message: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_chat_message + +.PHONY: test-integration-imbot-event +test-integration-imbot-event: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_event + +.PHONY: test-integration-imbot-file +test-integration-imbot-file: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_file + +.PHONY: test-integration-scope-im +test-integration-scope-im: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_im + +.PHONY: test-integration-im-disk +test-integration-im-disk: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_disk + +.PHONY: test-integration-im-message +test-integration-im-message: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_message + +.PHONY: test-integration-im-dialog +test-integration-im-dialog: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_dialog + +.PHONY: test-integration-im-department +test-integration-im-department: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_department + +.PHONY: test-integration-im-user +test-integration-im-user: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_user + +.PHONY: test-integration-im-revision +test-integration-im-revision: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_revision + +.PHONY: test-integration-im-counters +test-integration-im-counters: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_counters + +.PHONY: test-integration-im-recent +test-integration-im-recent: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_recent + +.PHONY: test-integration-im-search +test-integration-im-search: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_search + +.PHONY: test-integration-im-user-status +test-integration-im-user-status: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_user_status + +.PHONY: test-integration-im-chat +test-integration-im-chat: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_chat + +.PHONY: test-integration-im-chat-user +test-integration-im-chat-user: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_chat_user + +.PHONY: test-integration-im-notify +test-integration-im-notify: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_notify + +.PHONY: test-integration-im-event-v2 +test-integration-im-event-v2: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_event_v2 + +.PHONY: test-integration-im-file-v2 +test-integration-im-file-v2: + docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_file_v2 + # work dev environment .PHONY: php-dev-server-up php-dev-server-up: diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e6ce503a..2383ee72 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -37,6 +37,7 @@ parameters: - tests/Integration/Services/CRM/Documentgenerator/Document - tests/Integration/Services/CRM/Documentgenerator/Template - tests/Integration/Services/Landing + - tests/Integration/Services/IMBot excludePaths: - tests/Integration/Services/CRM/Requisites/Service/RequisiteUserfieldUseCaseTest.php - tests/Integration/Services/CRM/Status diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d0187ae5..23295d86 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -262,6 +262,78 @@ ./tests/Integration/Services/Landing/RepoWidget/ + + ./tests/Integration/Services/IMBot/ + + + ./tests/Integration/Services/IMBot/Bot/ + + + ./tests/Integration/Services/IMBot/Chat/ + + + ./tests/Integration/Services/IMBot/Command/ + + + ./tests/Integration/Services/IMBot/Revision/ + + + ./tests/Integration/Services/IMBot/ChatMessage/ + + + ./tests/Integration/Services/IMBot/Event/ + + + ./tests/Integration/Services/IMBot/File/ + + + ./tests/Integration/Services/IM/ + + + ./tests/Integration/Services/IM/Disk/ + + + ./tests/Integration/Services/IM/Chat/ + + + ./tests/Integration/Services/IM/Chat/Service/ChatUserTest.php + + + ./tests/Integration/Services/IM/Dialog/ + + + ./tests/Integration/Services/IM/Department/ + + + ./tests/Integration/Services/IM/Message/ + + + ./tests/Integration/Services/IM/Revision/ + + + ./tests/Integration/Services/IM/Counters/ + + + ./tests/Integration/Services/IM/Recent/ + + + ./tests/Integration/Services/IM/Search/ + + + ./tests/Integration/Services/IM/User/ + + + ./tests/Integration/Services/IM/User/ + + + ./tests/Integration/Services/IM/Notify/ + + + ./tests/Integration/Services/IM/EventV2/ + + + ./tests/Integration/Services/IM/FileV2/ + diff --git a/rector.php b/rector.php index fb38e858..a87c7982 100644 --- a/rector.php +++ b/rector.php @@ -78,6 +78,8 @@ __DIR__ . '/tests/Integration/Services/CRM/Documentgenerator/Template', __DIR__ . '/src/Services/Landing', __DIR__ . '/tests/Integration/Services/Landing', + __DIR__ . '/src/Services/IMBot', + __DIR__ . '/tests/Integration/Services/IMBot', __DIR__ . '/tests/Unit/', ]) ->withCache(cacheDirectory: __DIR__ . '/var/.cache/rector') diff --git a/src/Services/IM/Chat/ChatColor.php b/src/Services/IM/Chat/ChatColor.php new file mode 100644 index 00000000..d5936ae3 --- /dev/null +++ b/src/Services/IM/Chat/ChatColor.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat; + +enum ChatColor: string +{ + case Red = 'RED'; + case Green = 'GREEN'; + case Mint = 'MINT'; + case LightBlue = 'LIGHT_BLUE'; + case DarkBlue = 'DARK_BLUE'; + case Purple = 'PURPLE'; + case Aqua = 'AQUA'; + case Pink = 'PINK'; + case Lime = 'LIME'; + case Brown = 'BROWN'; + case Azure = 'AZURE'; + case Khaki = 'KHAKI'; + case Sand = 'SAND'; + case Marengo = 'MARENGO'; + case Gray = 'GRAY'; + case Graphite = 'GRAPHITE'; +} diff --git a/src/Services/IM/Chat/ChatEntityType.php b/src/Services/IM/Chat/ChatEntityType.php new file mode 100644 index 00000000..ca283dee --- /dev/null +++ b/src/Services/IM/Chat/ChatEntityType.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat; + +enum ChatEntityType: string +{ + case VideoConf = 'VIDEOCONF'; + case AiAssistantPrivate = 'AI_ASSISTANT_PRIVATE'; + case Lines = 'LINES'; + case LiveChat = 'LIVECHAT'; + case Announcement = 'ANNOUNCEMENT'; + case Calendar = 'CALENDAR'; + case Mail = 'MAIL'; + case Crm = 'CRM'; + case SonetGroup = 'SONET_GROUP'; + case Tasks = 'TASKS'; + case TasksTask = 'TASKS_TASK'; + case Call = 'CALL'; +} diff --git a/src/Services/IM/Chat/ChatType.php b/src/Services/IM/Chat/ChatType.php new file mode 100644 index 00000000..7a03f1dc --- /dev/null +++ b/src/Services/IM/Chat/ChatType.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat; + +enum ChatType: string +{ + case Open = 'OPEN'; + // API value 'CHAT' denotes a closed / non-public chat. + case Closed = 'CHAT'; +} diff --git a/src/Services/IM/Chat/Result/ChatItemResult.php b/src/Services/IM/Chat/Result/ChatItemResult.php new file mode 100644 index 00000000..20b7a021 --- /dev/null +++ b/src/Services/IM/Chat/Result/ChatItemResult.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $ID + */ +class ChatItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Chat/Result/ChatResult.php b/src/Services/IM/Chat/Result/ChatResult.php new file mode 100644 index 00000000..9d6fe12b --- /dev/null +++ b/src/Services/IM/Chat/Result/ChatResult.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class ChatResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function chat(): ?ChatItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + if (!array_key_exists('ID', $result)) { + return null; + } + + return new ChatItemResult($result); + } +} diff --git a/src/Services/IM/Chat/Result/ChatUserListResult.php b/src/Services/IM/Chat/Result/ChatUserListResult.php new file mode 100644 index 00000000..80b5587d --- /dev/null +++ b/src/Services/IM/Chat/Result/ChatUserListResult.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class ChatUserListResult extends AbstractResult +{ + /** + * @return int[] + * @throws BaseException + */ + public function getUserIds(): array + { + return array_map( + static fn (mixed $id): int => (int)$id, + $this->getCoreResponse()->getResponseData()->getResult() + ); + } +} diff --git a/src/Services/IM/Chat/Service/Chat.php b/src/Services/IM/Chat/Service/Chat.php new file mode 100644 index 00000000..3fa5e0b4 --- /dev/null +++ b/src/Services/IM/Chat/Service/Chat.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\AddedItemResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Chat\ChatColor; +use Bitrix24\SDK\Services\IM\Chat\ChatEntityType; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Result\ChatResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Chat extends AbstractService +{ + /** + * @param positive-int[] $users + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.add', + 'https://apidocs.bitrix24.com/api-reference/chats/im-chat-add.html', + 'Create a new chat' + )] + public function add( + array $users, + ?ChatType $chatType = null, + ?string $title = null, + ?string $description = null, + ?ChatColor $chatColor = null, + ?string $message = null, + ?string $avatar = null, + ?ChatEntityType $chatEntityType = null, + ?string $entityId = null, + ?string $copilotMainRole = null, + ): AddedItemResult { + $payload = ['USERS' => $users]; + if ($chatType instanceof ChatType) { + $payload['TYPE'] = $chatType->value; + } + + if ($title !== null) { + $payload['TITLE'] = $title; + } + + if ($description !== null) { + $payload['DESCRIPTION'] = $description; + } + + if ($chatColor instanceof ChatColor) { + $payload['COLOR'] = $chatColor->value; + } + + if ($message !== null) { + $payload['MESSAGE'] = $message; + } + + if ($avatar !== null) { + $payload['AVATAR'] = $avatar; + } + + if ($chatEntityType instanceof ChatEntityType) { + $payload['ENTITY_TYPE'] = $chatEntityType->value; + } + + if ($entityId !== null) { + $payload['ENTITY_ID'] = $entityId; + } + + if ($copilotMainRole !== null) { + $payload['COPILOT_MAIN_ROLE'] = $copilotMainRole; + } + + return new AddedItemResult($this->core->call('im.chat.add', $payload)); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.get', + 'https://apidocs.bitrix24.com/api-reference/chats/im-chat-get.html', + 'Get chat id by linked entity type and id' + )] + public function get(ChatEntityType $chatEntityType, string $entityId): ChatResult + { + return new ChatResult($this->core->call('im.chat.get', [ + 'ENTITY_TYPE' => $chatEntityType->value, + 'ENTITY_ID' => $entityId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.leave', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-users/im-chat-leave.html', + 'Remove the current user from a chat' + )] + public function leave(int $chatId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.leave', [ + 'CHAT_ID' => $chatId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.mute', + 'https://apidocs.bitrix24.com/api-reference/chats/special-operations/im-chat-mute.html', + 'Mute/unmute notifications in a chat by chat id' + )] + public function mute(int $chatId, bool $mute = true): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.mute', [ + 'CHAT_ID' => $chatId, + 'MUTE' => $mute ? 'Y' : 'N', + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.mute', + 'https://apidocs.bitrix24.com/api-reference/chats/special-operations/im-chat-mute.html', + 'Mute/unmute notifications in a chat by dialog id' + )] + public function muteByDialog(string $dialogId, bool $mute = true): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.mute', [ + 'DIALOG_ID' => $dialogId, + 'MUTE' => $mute ? 'Y' : 'N', + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.setOwner', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-update/im-chat-set-owner.html', + 'Change chat owner' + )] + public function setOwner(int $chatId, int $userId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.setOwner', [ + 'CHAT_ID' => $chatId, + 'USER_ID' => $userId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.updateAvatar', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-update/im-chat-update-avatar.html', + 'Update chat avatar' + )] + public function updateAvatar(int $chatId, string $avatar): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.updateAvatar', [ + 'CHAT_ID' => $chatId, + 'AVATAR' => $avatar, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.updateColor', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-update/im-chat-update-color.html', + 'Update chat color (mobile app)' + )] + public function updateColor(int $chatId, ChatColor $chatColor): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.updateColor', [ + 'CHAT_ID' => $chatId, + 'COLOR' => $chatColor->value, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.updateTitle', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-update/im-chat-update-title.html', + 'Update chat title' + )] + public function updateTitle(int $chatId, string $title): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.updateTitle', [ + 'CHAT_ID' => $chatId, + 'TITLE' => $title, + ])); + } +} diff --git a/src/Services/IM/Chat/Service/ChatUser.php b/src/Services/IM/Chat/Service/ChatUser.php new file mode 100644 index 00000000..7d6ae993 --- /dev/null +++ b/src/Services/IM/Chat/Service/ChatUser.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Chat\Result\ChatUserListResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class ChatUser extends AbstractService +{ + /** + * @param positive-int $chatId + * @param positive-int[] $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.user.add', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-users/im-chat-user-add.html', + 'Add participants to a chat' + )] + public function add(int $chatId, array $userIds, bool $hideHistory = true): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.user.add', [ + 'CHAT_ID' => $chatId, + 'USERS' => $userIds, + 'HIDE_HISTORY' => $hideHistory ? 'Y' : 'N', + ])); + } + + /** + * @param positive-int $chatId + * @param positive-int $userId + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.user.delete', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-users/im-chat-user-delete.html', + 'Remove a participant from a chat' + )] + public function delete(int $chatId, int $userId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.chat.user.delete', [ + 'CHAT_ID' => $chatId, + 'USER_ID' => $userId, + ])); + } + + /** + * @param positive-int $chatId + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.chat.user.list', + 'https://apidocs.bitrix24.com/api-reference/chats/chat-users/im-chat-user-list.html', + 'List participant user IDs of a chat' + )] + public function list(int $chatId): ChatUserListResult + { + return new ChatUserListResult($this->core->call('im.chat.user.list', [ + 'CHAT_ID' => $chatId, + ])); + } +} diff --git a/src/Services/IM/Counters/Result/CountersItemResult.php b/src/Services/IM/Counters/Result/CountersItemResult.php new file mode 100644 index 00000000..e4c375e9 --- /dev/null +++ b/src/Services/IM/Counters/Result/CountersItemResult.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Counters\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read array $TYPE + * @property-read array $CHAT + * @property-read array $CHAT_MUTED + * @property-read array $CHAT_UNREAD + * @property-read array $LINES + * @property-read array $DIALOG + * @property-read array $DIALOG_UNREAD + * @property-read array $COLLAB + * @property-read array $COLLAB_UNREAD + * @property-read array $COPILOT_UNREAD + * @property-read array $TASKS_TASK_UNREAD + * @property-read array $COPILOT + * @property-read array $CHANNEL_COMMENT + * @property-read array $TASKS_TASK + */ +class CountersItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Counters/Result/CountersResult.php b/src/Services/IM/Counters/Result/CountersResult.php new file mode 100644 index 00000000..04b7db41 --- /dev/null +++ b/src/Services/IM/Counters/Result/CountersResult.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Counters\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class CountersResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function counters(): CountersItemResult + { + return new CountersItemResult( + $this->getCoreResponse()->getResponseData()->getResult() + ); + } +} diff --git a/src/Services/IM/Counters/Service/Counters.php b/src/Services/IM/Counters/Service/Counters.php new file mode 100644 index 00000000..c7f88eb1 --- /dev/null +++ b/src/Services/IM/Counters/Service/Counters.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Counters\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Counters\Result\CountersResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Counters extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.counters.get', + 'https://apidocs.bitrix24.com/api-reference/chats/im-counters-get.html', + 'Get unread message and notification counters for the current user' + )] + public function get(): CountersResult + { + return new CountersResult($this->core->call('im.counters.get')); + } +} diff --git a/src/Services/IM/Department/Result/DepartmentItemResult.php b/src/Services/IM/Department/Result/DepartmentItemResult.php new file mode 100644 index 00000000..6a2e55db --- /dev/null +++ b/src/Services/IM/Department/Result/DepartmentItemResult.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Department\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read string $name + * @property-read string $full_name + * @property-read int $manager_user_id + * @property-read array|null $manager_user_data + */ +class DepartmentItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Department/Result/DepartmentUsersByDepartmentResult.php b/src/Services/IM/Department/Result/DepartmentUsersByDepartmentResult.php new file mode 100644 index 00000000..40d75cbd --- /dev/null +++ b/src/Services/IM/Department/Result/DepartmentUsersByDepartmentResult.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Department\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\IM\User\Result\UserItemResult; + +class DepartmentUsersByDepartmentResult extends AbstractResult +{ + /** + * @return array + * @throws BaseException + */ + public function userIdsByDepartment(): array + { + $usersByDepartment = []; + + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $departmentId => $items) { + if (!is_array($items)) { + continue; + } + + $usersByDepartment[(int)$departmentId] = array_values(array_filter($items, 'is_int')); + } + + return $usersByDepartment; + } + + /** + * @return array + * @throws BaseException + */ + public function usersByDepartment(): array + { + $usersByDepartment = []; + + foreach ($this->getCoreResponse()->getResponseData()->getResult() as $departmentId => $items) { + if (!is_array($items)) { + continue; + } + + $usersByDepartment[(int)$departmentId] = array_values(array_map( + static fn (array $user): UserItemResult => new UserItemResult($user), + array_filter($items, 'is_array') + )); + } + + return $usersByDepartment; + } +} diff --git a/src/Services/IM/Department/Result/DepartmentUsersResult.php b/src/Services/IM/Department/Result/DepartmentUsersResult.php new file mode 100644 index 00000000..5105ec2a --- /dev/null +++ b/src/Services/IM/Department/Result/DepartmentUsersResult.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Department\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; +use Bitrix24\SDK\Services\IM\User\Result\UserItemResult; + +class DepartmentUsersResult extends AbstractResult +{ + /** + * @return int[] + * @throws BaseException + */ + public function userIds(): array + { + return array_values(array_filter( + $this->getCoreResponse()->getResponseData()->getResult(), + 'is_int' + )); + } + + /** + * @return UserItemResult[] + * @throws BaseException + */ + public function users(): array + { + return array_values(array_map( + static fn (array $user): UserItemResult => new UserItemResult($user), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } + + /** + * @throws BaseException + */ + public function total(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } + + /** + * @throws BaseException + */ + public function next(): ?int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getNextItem(); + } +} diff --git a/src/Services/IM/Department/Result/DepartmentsResult.php b/src/Services/IM/Department/Result/DepartmentsResult.php new file mode 100644 index 00000000..32bb02f8 --- /dev/null +++ b/src/Services/IM/Department/Result/DepartmentsResult.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Department\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DepartmentsResult extends AbstractResult +{ + /** + * @return DepartmentItemResult[] + * @throws BaseException + */ + public function items(): array + { + return array_values(array_map( + static fn (array $item): DepartmentItemResult => new DepartmentItemResult($item), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } +} diff --git a/src/Services/IM/Department/Service/Department.php b/src/Services/IM/Department/Service/Department.php new file mode 100644 index 00000000..e12cc9a7 --- /dev/null +++ b/src/Services/IM/Department/Service/Department.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Department\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Department\Result\DepartmentsResult; +use Bitrix24\SDK\Services\IM\Department\Result\DepartmentUsersByDepartmentResult; +use Bitrix24\SDK\Services\IM\Department\Result\DepartmentUsersResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Department extends AbstractService +{ + /** + * @param int[] $departmentIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.department.get', + 'https://apidocs.bitrix24.com/api-reference/chats/departments/im-department-get.html', + 'Get IM department data by IDs' + )] + public function get(array $departmentIds, bool $userData = false): DepartmentsResult + { + return new DepartmentsResult($this->core->call('im.department.get', [ + 'ID' => $departmentIds, + 'USER_DATA' => $userData ? 'Y' : 'N', + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.department.colleagues.list', + 'https://apidocs.bitrix24.com/api-reference/chats/departments/im-department-colleagues-list.html', + 'List colleagues of the current user' + )] + public function colleaguesList( + bool $userData = false, + ?int $offset = null, + ?int $limit = null, + ): DepartmentUsersResult { + $payload = [ + 'USER_DATA' => $userData ? 'Y' : 'N', + 'OFFSET' => $offset, + 'LIMIT' => $limit, + ]; + + return new DepartmentUsersResult($this->core->call('im.department.colleagues.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @param int[] $departmentIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.department.employees.get', + 'https://apidocs.bitrix24.com/api-reference/chats/departments/im-department-employees-get.html', + 'Get employees for IM departments' + )] + public function employeesGet(array $departmentIds, bool $userData = false): DepartmentUsersByDepartmentResult + { + return new DepartmentUsersByDepartmentResult($this->core->call('im.department.employees.get', [ + 'ID' => $departmentIds, + 'USER_DATA' => $userData ? 'Y' : 'N', + ])); + } + + /** + * @param int[] $departmentIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.department.managers.get', + 'https://apidocs.bitrix24.com/api-reference/chats/departments/im-department-managers-get.html', + 'Get managers for IM departments' + )] + public function managersGet(array $departmentIds, bool $userData = false): DepartmentUsersByDepartmentResult + { + return new DepartmentUsersByDepartmentResult($this->core->call('im.department.managers.get', [ + 'ID' => $departmentIds, + 'USER_DATA' => $userData ? 'Y' : 'N', + ])); + } +} diff --git a/src/Services/IM/Dialog/Result/DialogActionResult.php b/src/Services/IM/Dialog/Result/DialogActionResult.php new file mode 100644 index 00000000..0a171506 --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogActionResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogActionResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()[0] ?? false); + } +} diff --git a/src/Services/IM/Dialog/Result/DialogItemResult.php b/src/Services/IM/Dialog/Result/DialogItemResult.php new file mode 100644 index 00000000..9c84d7da --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogItemResult.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read int $parent_chat_id + * @property-read int $parent_message_id + * @property-read string $name + * @property-read string|null $description + * @property-read int $owner + * @property-read bool $extranet + * @property-read string $avatar + * @property-read string $color + * @property-read string $type + * @property-read int $counter + * @property-read int $user_counter + * @property-read int $message_count + * @property-read int $unread_id + * @property-read array $restrictions + * @property-read int $last_message_id + * @property-read int $last_id + * @property-read int $marked_id + * @property-read int $disk_folder_id + * @property-read string $entity_type + * @property-read string $entity_id + * @property-read string $entity_data_1 + * @property-read string $entity_data_2 + * @property-read string $entity_data_3 + * @property-read array $mute_list + * @property-read CarbonImmutable $date_create + * @property-read string $message_type + * @property-read string $public + * @property-read string $role + * @property-read array $entity_link + * @property-read bool $text_field_enabled + * @property-read int|null $background_id + * @property-read array $permissions + * @property-read bool $is_new + * @property-read array $readed_list + * @property-read array $manager_list + * @property-read array $last_message_views + * @property-read string $dialog_id + */ +class DialogItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Dialog/Result/DialogMessageSearchResult.php b/src/Services/IM/Dialog/Result/DialogMessageSearchResult.php new file mode 100644 index 00000000..c89c1dad --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogMessageSearchResult.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogMessageSearchResult extends AbstractResult +{ + /** + * @return MessageItemResult[] + * @throws BaseException + */ + public function messages(): array + { + return array_map( + static fn (array $message): MessageItemResult => new MessageItemResult($message), + array_filter($this->payload()['messages'] ?? [], 'is_array') + ); + } + + /** + * @return array> + * @throws BaseException + */ + public function users(): array + { + return array_values(array_filter($this->payload()['users'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function files(): array + { + return array_values(array_filter($this->payload()['files'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function additionalMessages(): array + { + return array_values(array_filter($this->payload()['additionalMessages'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function stickers(): array + { + return array_values(array_filter($this->payload()['stickers'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function reactions(): array + { + return array_values(array_filter($this->payload()['reactions'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function usersShort(): array + { + return array_values(array_filter($this->payload()['usersShort'] ?? [], 'is_array')); + } + + /** + * @return array|null + * @throws BaseException + */ + public function copilot(): ?array + { + $copilot = $this->payload()['copilot'] ?? null; + + return is_array($copilot) ? $copilot : null; + } + + /** + * @return array|null + * @throws BaseException + */ + public function tariffRestrictions(): ?array + { + $tariffRestrictions = $this->payload()['tariffRestrictions'] ?? null; + + return is_array($tariffRestrictions) ? $tariffRestrictions : null; + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_is_list($result) && is_array($result[0] ?? null) ? $result[0] : $result; + } +} diff --git a/src/Services/IM/Dialog/Result/DialogMessagesResult.php b/src/Services/IM/Dialog/Result/DialogMessagesResult.php new file mode 100644 index 00000000..5cb5da10 --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogMessagesResult.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogMessagesResult extends AbstractResult +{ + /** + * @return MessageItemResult[] + * @throws BaseException + */ + public function messages(): array + { + $messages = $this->payload()['messages'] ?? []; + + return array_map( + static fn (array $message): MessageItemResult => new MessageItemResult($message), + array_filter($messages, 'is_array') + ); + } + + /** + * @throws BaseException + */ + public function chatId(): ?int + { + $chatId = $this->payload()['chat_id'] ?? null; + + return is_int($chatId) ? $chatId : null; + } + + /** + * @return array> + * @throws BaseException + */ + public function users(): array + { + return array_values(array_filter($this->payload()['users'] ?? [], 'is_array')); + } + + /** + * @return array> + * @throws BaseException + */ + public function files(): array + { + return array_values(array_filter($this->payload()['files'] ?? [], 'is_array')); + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_is_list($result) && is_array($result[0] ?? null) ? $result[0] : $result; + } +} diff --git a/src/Services/IM/Dialog/Result/DialogReadResult.php b/src/Services/IM/Dialog/Result/DialogReadResult.php new file mode 100644 index 00000000..ccb641d7 --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogReadResult.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogReadResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function readState(): ?DialogReadStateItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + $payload = array_is_list($result) && is_array($result[0] ?? null) ? $result[0] : $result; + + if ($payload === []) { + return null; + } + + return new DialogReadStateItemResult($payload); + } +} diff --git a/src/Services/IM/Dialog/Result/DialogReadStateItemResult.php b/src/Services/IM/Dialog/Result/DialogReadStateItemResult.php new file mode 100644 index 00000000..6a49a869 --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogReadStateItemResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read string $dialogId + * @property-read int $chatId + * @property-read int $lastId + * @property-read int $counter + */ +class DialogReadStateItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Dialog/Result/DialogResult.php b/src/Services/IM/Dialog/Result/DialogResult.php new file mode 100644 index 00000000..ad11e2fe --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogResult.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function dialog(): ?DialogItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + if (!array_is_list($result)) { + return new DialogItemResult($result); + } + + if (!is_array($result[0] ?? null)) { + return null; + } + + return new DialogItemResult($result[0]); + } +} diff --git a/src/Services/IM/Dialog/Result/DialogUserItemResult.php b/src/Services/IM/Dialog/Result/DialogUserItemResult.php new file mode 100644 index 00000000..7875f2bb --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogUserItemResult.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read bool $active + * @property-read string $name + * @property-read string $first_name + * @property-read string $last_name + * @property-read string|null $work_position + * @property-read string $color + * @property-read string $avatar + * @property-read string $avatar_hr + * @property-read string $gender + * @property-read string $birthday + * @property-read bool $extranet + * @property-read bool $network + * @property-read bool $bot + * @property-read bool $connector + * @property-read string $external_auth_id + * @property-read string $status + * @property-read bool $idle + * @property-read string $last_activity_date + * @property-read bool $mobile_last_date + * @property-read bool $desktop_last_date + * @property-read bool $absent + * @property-read array $departments + * @property-read array $phones + * @property-read array|null $bot_data + * @property-read string $type + * @property-read string $website + * @property-read string $email + */ +class DialogUserItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Dialog/Result/DialogUsersResult.php b/src/Services/IM/Dialog/Result/DialogUsersResult.php new file mode 100644 index 00000000..8f679b09 --- /dev/null +++ b/src/Services/IM/Dialog/Result/DialogUsersResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class DialogUsersResult extends AbstractResult +{ + /** + * @return DialogUserItemResult[] + * @throws BaseException + */ + public function users(): array + { + return array_map( + static fn (array $user): DialogUserItemResult => new DialogUserItemResult($user), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + ); + } + + /** + * @throws BaseException + */ + public function total(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } + + /** + * @throws BaseException + */ + public function next(): ?int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getNextItem(); + } +} diff --git a/src/Services/IM/Dialog/Result/MessageItemResult.php b/src/Services/IM/Dialog/Result/MessageItemResult.php new file mode 100644 index 00000000..0ea8b285 --- /dev/null +++ b/src/Services/IM/Dialog/Result/MessageItemResult.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read int $chat_id + * @property-read int|null $chatId + * @property-read int $author_id + * @property-read int|null $authorId + * @property-read CarbonImmutable $date + * @property-read string $text + * @property-read bool|null $isSystem + * @property-read bool $unread + * @property-read string|null $uuid + * @property-read array|null $replaces + * @property-read array|null $forward + * @property-read array $params + * @property-read bool|null $viewedByOthers + * @property-read bool|null $viewed + * @property-read CarbonImmutable|null $disappearing_date + */ +class MessageItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Dialog/Service/Dialog.php b/src/Services/IM/Dialog/Service/Dialog.php new file mode 100644 index 00000000..2d4d8456 --- /dev/null +++ b/src/Services/IM/Dialog/Service/Dialog.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Dialog\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogActionResult; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogMessageSearchResult; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogMessagesResult; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogReadResult; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogResult; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogUsersResult; +use Carbon\CarbonImmutable; + +#[ApiServiceMetadata(new Scope(['im']))] +class Dialog extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.get', + 'https://apidocs.bitrix24.ru/api-reference/chats/im-dialog-get.html', + 'Get dialog information' + )] + public function get(string $dialogId): DialogResult + { + return new DialogResult($this->core->call('im.dialog.get', [ + 'DIALOG_ID' => $dialogId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.messages.get', + 'https://apidocs.bitrix24.ru/api-reference/chats/messages/im-dialog-messages-get.html', + 'Get dialog messages' + )] + public function messagesGet( + string $dialogId, + ?int $lastId = null, + ?int $firstId = null, + ?int $limit = null, + ): DialogMessagesResult { + $payload = [ + 'DIALOG_ID' => $dialogId, + 'LAST_ID' => $lastId, + 'FIRST_ID' => $firstId, + 'LIMIT' => $limit, + ]; + + return new DialogMessagesResult($this->core->call('im.dialog.messages.get', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @param array|null $order + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.messages.search', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-dialog-messages-search.html', + 'Search messages in a chat' + )] + public function messagesSearch( + int $chatId, + ?string $searchMessage = null, + ?CarbonImmutable $dateFrom = null, + ?CarbonImmutable $dateTo = null, + ?CarbonImmutable $date = null, + ?array $order = null, + ?int $limit = null, + ?int $lastId = null, + ): DialogMessageSearchResult { + $payload = [ + 'CHAT_ID' => $chatId, + 'SEARCH_MESSAGE' => $searchMessage, + 'DATE_FROM' => $dateFrom?->toAtomString(), + 'DATE_TO' => $dateTo?->toAtomString(), + 'DATE' => $date?->toAtomString(), + 'ORDER' => $order, + 'LIMIT' => $limit, + 'LAST_ID' => $lastId, + ]; + + return new DialogMessageSearchResult($this->core->call('im.dialog.messages.search', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.read', + 'https://apidocs.bitrix24.ru/api-reference/chats/messages/im-dialog-read.html', + 'Mark dialog messages as read' + )] + public function read(string $dialogId, ?int $messageId = null): DialogReadResult + { + $payload = ['DIALOG_ID' => $dialogId]; + if ($messageId !== null) { + $payload['MESSAGE_ID'] = $messageId; + } + + return new DialogReadResult($this->core->call('im.dialog.read', $payload)); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.read.all', + 'https://apidocs.bitrix24.ru/api-reference/chats/special-operations/im-dialog-read-all.html', + 'Mark all dialogs as read' + )] + public function readAll(): DialogActionResult + { + return new DialogActionResult($this->core->call('im.dialog.read.all', [])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.unread', + 'https://apidocs.bitrix24.ru/api-reference/chats/messages/im-dialog-unread.html', + 'Mark dialog messages as unread' + )] + public function unread(string $dialogId, int $messageId): DialogActionResult + { + return new DialogActionResult($this->core->call('im.dialog.unread', [ + 'DIALOG_ID' => $dialogId, + 'MESSAGE_ID' => $messageId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.users.list', + 'https://apidocs.bitrix24.ru/api-reference/chats/chat-users/im-dialog-users-list.html', + 'List dialog participants' + )] + public function usersList( + string $dialogId, + bool $skipExternal = false, + ?string $skipExternalExceptTypes = null, + ?int $limit = null, + ?int $lastId = null, + ?int $offset = null, + ): DialogUsersResult { + $payload = [ + 'DIALOG_ID' => $dialogId, + 'SKIP_EXTERNAL' => $skipExternal ? 'Y' : 'N', + 'SKIP_EXTERNAL_EXCEPT_TYPES' => $skipExternalExceptTypes, + 'LIMIT' => $limit, + 'LAST_ID' => $lastId, + 'OFFSET' => $offset, + ]; + + return new DialogUsersResult($this->core->call('im.dialog.users.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.dialog.writing', + 'https://apidocs.bitrix24.ru/api-reference/chats/messages/im-dialog-writing.html', + 'Send typing indicator' + )] + public function writing(string $dialogId): DialogActionResult + { + return new DialogActionResult($this->core->call('im.dialog.writing', [ + 'DIALOG_ID' => $dialogId, + ])); + } +} diff --git a/src/Services/IM/Disk/Result/FileCommitResult.php b/src/Services/IM/Disk/Result/FileCommitResult.php new file mode 100644 index 00000000..fe38f79c --- /dev/null +++ b/src/Services/IM/Disk/Result/FileCommitResult.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +final class FileCommitResult extends AbstractResult +{ + /** + * @return array> + * @throws BaseException + */ + public function files(): array + { + $files = $this->payload()['FILES'] ?? []; + + return is_array($files) ? array_filter($files, 'is_array') : []; + } + + /** + * @return list + * @throws BaseException + */ + public function diskIds(): array + { + $diskIds = $this->payload()['DISK_ID'] ?? []; + + if (!is_array($diskIds)) { + return []; + } + + return array_values(array_map('intval', $diskIds)); + } + + /** + * @return array> + * @throws BaseException + */ + public function fileModels(): array + { + $fileModels = $this->payload()['FILE_MODELS'] ?? []; + + return is_array($fileModels) ? array_filter($fileModels, 'is_array') : []; + } + + /** + * @throws BaseException + */ + public function messageId(): ?int + { + $payload = $this->payload(); + if (isset($payload['MESSAGE_ID'])) { + return (int)$payload['MESSAGE_ID']; + } + + foreach ($this->fileModels() as $fileModel) { + if (isset($fileModel['MESSAGE_ID'])) { + return (int)$fileModel['MESSAGE_ID']; + } + } + + return null; + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $payload = $this->getCoreResponse()->getResponseData()->getResult(); + + return is_array($payload) ? $payload : []; + } +} diff --git a/src/Services/IM/Disk/Result/FileDeleteResult.php b/src/Services/IM/Disk/Result/FileDeleteResult.php new file mode 100644 index 00000000..10f6de13 --- /dev/null +++ b/src/Services/IM/Disk/Result/FileDeleteResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +final class FileDeleteResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()[0] ?? false); + } +} diff --git a/src/Services/IM/Disk/Result/FileSaveResult.php b/src/Services/IM/Disk/Result/FileSaveResult.php new file mode 100644 index 00000000..b2e6c661 --- /dev/null +++ b/src/Services/IM/Disk/Result/FileSaveResult.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +final class FileSaveResult extends AbstractResult +{ + /** + * @return array + * @throws BaseException + */ + public function folder(): array + { + $folder = $this->payload()['folder'] ?? []; + + return is_array($folder) ? $folder : []; + } + + /** + * @return array + * @throws BaseException + */ + public function file(): array + { + $file = $this->payload()['file'] ?? []; + + return is_array($file) ? $file : []; + } + + /** + * @throws BaseException + */ + public function folderId(): ?int + { + $folder = $this->folder(); + + return isset($folder['id']) ? (int)$folder['id'] : null; + } + + /** + * @throws BaseException + */ + public function fileId(): ?int + { + $file = $this->file(); + + return isset($file['id']) ? (int)$file['id'] : null; + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $payload = $this->getCoreResponse()->getResponseData()->getResult(); + + return is_array($payload) ? $payload : []; + } +} diff --git a/src/Services/IM/Disk/Result/FolderIdResult.php b/src/Services/IM/Disk/Result/FolderIdResult.php new file mode 100644 index 00000000..1b66c7d1 --- /dev/null +++ b/src/Services/IM/Disk/Result/FolderIdResult.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Result; + +use Bitrix24\SDK\Core\Result\AbstractResult; + +class FolderIdResult extends AbstractResult +{ + public function getId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['ID']; + } +} diff --git a/src/Services/IM/Disk/Result/RecordShareResult.php b/src/Services/IM/Disk/Result/RecordShareResult.php new file mode 100644 index 00000000..aa994f86 --- /dev/null +++ b/src/Services/IM/Disk/Result/RecordShareResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +final class RecordShareResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()[0] ?? false); + } +} diff --git a/src/Services/IM/Disk/Service/Disk.php b/src/Services/IM/Disk/Service/Disk.php new file mode 100644 index 00000000..15e3e113 --- /dev/null +++ b/src/Services/IM/Disk/Service/Disk.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Disk\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Disk\Result\FileCommitResult; +use Bitrix24\SDK\Services\IM\Disk\Result\FileDeleteResult; +use Bitrix24\SDK\Services\IM\Disk\Result\FileSaveResult; +use Bitrix24\SDK\Services\IM\Disk\Result\FolderIdResult; +use Bitrix24\SDK\Services\IM\Disk\Result\RecordShareResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Disk extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.disk.folder.get', + 'https://apidocs.bitrix24.com/api-reference/chats/files/im-disk-folder-get.html', + 'Get the identifier of the folder where chat files are stored' + )] + public function getFolderId(?int $chatId = null, ?string $dialogId = null): FolderIdResult + { + $params = []; + + if ($chatId !== null) { + $params['CHAT_ID'] = $chatId; + } + + if ($dialogId !== null) { + $params['DIALOG_ID'] = $dialogId; + } + + return new FolderIdResult($this->core->call('im.disk.folder.get', $params)); + } + + /** + * @param int|list|null $fileId + * @param int|list|null $uploadId + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.disk.file.commit', + 'https://apidocs.bitrix24.com/api-reference/chats/files/im-disk-file-commit.html', + 'Add one or more Disk files to a chat' + )] + public function commitFile( + ?int $chatId = null, + ?string $dialogId = null, + int|array|null $fileId = null, + int|array|null $uploadId = null, + ?string $message = null, + ?bool $silentMode = null, + ?bool $asFile = null, + ): FileCommitResult { + return new FileCommitResult($this->core->call('im.disk.file.commit', $this->filterNullValues([ + 'CHAT_ID' => $chatId, + 'DIALOG_ID' => $dialogId, + 'FILE_ID' => $fileId, + 'UPLOAD_ID' => $uploadId, + 'MESSAGE' => $message, + 'SILENT_MODE' => $silentMode === null ? null : ($silentMode ? 'Y' : 'N'), + 'AS_FILE' => $asFile === null ? null : ($asFile ? 'Y' : 'N'), + ]))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.disk.file.delete', + 'https://apidocs.bitrix24.com/api-reference/chats/files/im-disk-file-delete.html', + 'Delete a file from a chat folder' + )] + public function deleteFile(int $chatId, int $fileId): FileDeleteResult + { + return new FileDeleteResult($this->core->call('im.disk.file.delete', [ + 'CHAT_ID' => $chatId, + 'FILE_ID' => $fileId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.disk.file.save', + 'https://apidocs.bitrix24.com/api-reference/chats/files/im-disk-file-save.html', + 'Save a chat file to the current user personal Disk' + )] + public function saveFile(int $fileId): FileSaveResult + { + return new FileSaveResult($this->core->call('im.disk.file.save', [ + 'FILE_ID' => $fileId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.disk.record.share', + '', + 'Share an IM record file to a dialog' + )] + public function shareRecord(string $dialogId, int $diskId): RecordShareResult + { + return new RecordShareResult($this->core->call('im.disk.record.share', [ + 'DIALOG_ID' => $dialogId, + 'DISK_ID' => $diskId, + ])); + } + + /** + * @param array $payload + * + * @return array + */ + private function filterNullValues(array $payload): array + { + return array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ); + } +} diff --git a/src/Services/IM/EventV2/Result/EventV2ItemResult.php b/src/Services/IM/EventV2/Result/EventV2ItemResult.php new file mode 100644 index 00000000..1b03854e --- /dev/null +++ b/src/Services/IM/EventV2/Result/EventV2ItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\EventV2\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * A single event item returned by im.v2.Event.get. + * + * @property-read int $eventId Event ID; pass as `offset` in the next call to acknowledge + * @property-read string $type Event type (e.g. ONIMV2MESSAGEADD) + * @property-read ?CarbonImmutable $date Date and time of the event + * @property-read array $data Event payload; structure depends on the event type + */ +class EventV2ItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/EventV2/Result/EventsV2Result.php b/src/Services/IM/EventV2/Result/EventsV2Result.php new file mode 100644 index 00000000..21c21e48 --- /dev/null +++ b/src/Services/IM/EventV2/Result/EventsV2Result.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\EventV2\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for im.v2.Event.get. + * + * Response shape: + * { + * result: { + * events: [{ eventId, type, date, data }], + * nextOffset: int, + * hasMore: bool + * } + * } + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-get.html + */ +class EventsV2Result extends AbstractResult +{ + /** + * Returns all events from the current page. + * + * @return list + * + * @throws BaseException + */ + public function getEvents(): array + { + $events = $this->getCoreResponse()->getResponseData()->getResult()['events'] ?? []; + + return array_map( + static fn (array $event): EventV2ItemResult => new EventV2ItemResult($event), + $events + ); + } + + /** + * Returns the offset to pass to the next im.v2.Event.get call to acknowledge processed events. + * Returns null when there are no events. + * + * @throws BaseException + */ + public function getNextOffset(): ?int + { + $value = $this->getCoreResponse()->getResponseData()->getResult()['nextOffset'] ?? null; + + return $value !== null ? (int)$value : null; + } + + /** + * Returns true when there are more unprocessed events in the queue. + * + * @throws BaseException + */ + public function isHasMore(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()['hasMore'] ?? false); + } +} diff --git a/src/Services/IM/EventV2/Service/EventV2.php b/src/Services/IM/EventV2/Service/EventV2.php new file mode 100644 index 00000000..0396b1df --- /dev/null +++ b/src/Services/IM/EventV2/Service/EventV2.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\EventV2\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\EmptyResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\EventV2\Result\EventsV2Result; + +/** + * IM v2 event subscription service. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/ + */ +#[ApiServiceMetadata(new Scope(['im']))] +class EventV2 extends AbstractService +{ + /** + * Subscribe the current user to message event recording. + * + * After subscribing, events become available via im.v2.Event.get. Idempotent. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-subscribe.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.v2.Event.subscribe', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-subscribe.html', + 'Subscribe the current user to message event recording' + )] + public function subscribe(): EmptyResult + { + return new EmptyResult($this->core->call('im.v2.Event.subscribe')); + } + + /** + * Unsubscribe the current user from message event recording. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-unsubscribe.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.v2.Event.unsubscribe', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-unsubscribe.html', + 'Unsubscribe the current user from message event recording' + )] + public function unsubscribe(): EmptyResult + { + return new EmptyResult($this->core->call('im.v2.Event.unsubscribe')); + } + + /** + * Poll pending message events for the current user. + * + * Pass 0 (or omit) on the first call. Use the returned nextOffset in subsequent calls + * to acknowledge already-processed events and move the queue cursor forward. + * + * @param int $offset Acknowledges processed events. Pass nextOffset from the previous response (0 on first call). + * @param int|null $limit Maximum events to return (1–1000). Default: 100. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.v2.Event.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/events/event-get.html', + 'Poll pending message events for the current user' + )] + public function get(int $offset = 0, ?int $limit = null): EventsV2Result + { + $params = ['offset' => $offset]; + + if ($limit !== null) { + $params['limit'] = $limit; + } + + return new EventsV2Result($this->core->call('im.v2.Event.get', $params)); + } +} diff --git a/src/Services/IM/FileV2/Result/FileV2DownloadResult.php b/src/Services/IM/FileV2/Result/FileV2DownloadResult.php new file mode 100644 index 00000000..7bbc13a7 --- /dev/null +++ b/src/Services/IM/FileV2/Result/FileV2DownloadResult.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\FileV2\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for im.v2.File.download. + * + * Response shape: { result: { downloadUrl: string } } + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/file-download.html + */ +class FileV2DownloadResult extends AbstractResult +{ + /** + * Returns a one-time download URL for the requested file. + * + * @throws BaseException + */ + public function getDownloadUrl(): string + { + return (string)$this->getCoreResponse()->getResponseData()->getResult()['downloadUrl']; + } +} diff --git a/src/Services/IM/FileV2/Result/FileV2ItemResult.php b/src/Services/IM/FileV2/Result/FileV2ItemResult.php new file mode 100644 index 00000000..5b598b02 --- /dev/null +++ b/src/Services/IM/FileV2/Result/FileV2ItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\FileV2\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * File item returned by im.v2.File.upload. + * + * @property-read int $id + * @property-read int $chatId + * @property-read string $name + * @property-read string $extension + * @property-read int $size + */ +class FileV2ItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/FileV2/Result/FileV2UploadResult.php b/src/Services/IM/FileV2/Result/FileV2UploadResult.php new file mode 100644 index 00000000..1f96071e --- /dev/null +++ b/src/Services/IM/FileV2/Result/FileV2UploadResult.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\FileV2\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for im.v2.File.upload. + * + * Response shape: { result: { file: {...}, messageId: int, chatId: int, dialogId: string } } + */ +class FileV2UploadResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function file(): FileV2ItemResult + { + return new FileV2ItemResult($this->getCoreResponse()->getResponseData()->getResult()['file']); + } + + /** + * @throws BaseException + */ + public function getMessageId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['messageId']; + } + + /** + * @throws BaseException + */ + public function getChatId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['chatId']; + } + + /** + * @throws BaseException + */ + public function getDialogId(): string + { + return (string)$this->getCoreResponse()->getResponseData()->getResult()['dialogId']; + } +} diff --git a/src/Services/IM/FileV2/Service/FileV2.php b/src/Services/IM/FileV2/Service/FileV2.php new file mode 100644 index 00000000..19d17a0a --- /dev/null +++ b/src/Services/IM/FileV2/Service/FileV2.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\FileV2\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\FileV2\Result\FileV2DownloadResult; +use Bitrix24\SDK\Services\IM\FileV2\Result\FileV2UploadResult; + +/** + * IM v2 file service. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/ + */ +#[ApiServiceMetadata(new Scope(['im']))] +class FileV2 extends AbstractService +{ + /** + * Upload a file to a chat. + * + * @param string $name File name with extension. + * @param string $content Base64-encoded file content. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/file-upload.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.v2.File.upload', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/file-upload.html', + 'Upload a file to a chat' + )] + public function upload( + string $dialogId, + string $name, + string $content, + ?string $message = null, + ): FileV2UploadResult { + $fields = [ + 'name' => $name, + 'content' => $content, + ]; + + if ($message !== null) { + $fields['message'] = $message; + } + + return new FileV2UploadResult($this->core->call('im.v2.File.upload', [ + 'dialogId' => $dialogId, + 'fields' => $fields, + ])); + } + + /** + * Get a download URL for a file in a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/file-download.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.v2.File.download', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/im.v2/files/file-download.html', + 'Get a download URL for a file in a chat' + )] + public function download(int $fileId): FileV2DownloadResult + { + return new FileV2DownloadResult($this->core->call('im.v2.File.download', ['fileId' => $fileId])); + } +} diff --git a/src/Services/IM/IMServiceBuilder.php b/src/Services/IM/IMServiceBuilder.php index a6ff2246..c5743c5a 100644 --- a/src/Services/IM/IMServiceBuilder.php +++ b/src/Services/IM/IMServiceBuilder.php @@ -16,11 +16,55 @@ use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata; use Bitrix24\SDK\Core\Credentials\Scope; use Bitrix24\SDK\Services\AbstractServiceBuilder; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Services\IM\Chat\Service\ChatUser; +use Bitrix24\SDK\Services\IM\Counters\Service\Counters; +use Bitrix24\SDK\Services\IM\Department\Service\Department; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Services\IM\Disk\Service\Disk; +use Bitrix24\SDK\Services\IM\Message\Service\Message; use Bitrix24\SDK\Services\IM\Notify\Service\Notify; -#[ApiServiceBuilderMetadata(new Scope(['im']))] +use Bitrix24\SDK\Services\IM\Placements\PlacementLocationCodes; +use Bitrix24\SDK\Services\IM\Placements\Placements; +use Bitrix24\SDK\Services\IM\Recent\Service\Recent; +use Bitrix24\SDK\Services\IM\Revision\Service\Revision; +use Bitrix24\SDK\Services\IM\Search\Service\Search; +use Bitrix24\SDK\Services\IM\User\Service\UserStatus; +use Bitrix24\SDK\Services\IM\User\Service\User; +use Bitrix24\SDK\Services\IM\EventV2\Service\EventV2; +use Bitrix24\SDK\Services\IM\FileV2\Service\FileV2; +use Bitrix24\SDK\Services\Placement\Service\Placement; +#[ApiServiceBuilderMetadata(new Scope(['im']))] class IMServiceBuilder extends AbstractServiceBuilder { + public function recent(): Recent + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Recent($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function search(): Search + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Search($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function disk(): Disk + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Disk($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + public function notify(): Notify { if (!isset($this->serviceCache[__METHOD__])) { @@ -29,4 +73,117 @@ public function notify(): Notify return $this->serviceCache[__METHOD__]; } -} \ No newline at end of file + + public function chat(): Chat + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Chat($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function chatUser(): ChatUser + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatUser($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function message(): Message + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Message($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function dialog(): Dialog + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Dialog($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function revision(): Revision + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Revision($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function counters(): Counters + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Counters($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function department(): Department + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Department($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function userStatus(): UserStatus + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new UserStatus($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function user(): User + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new User($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function placementLocationCodes(): PlacementLocationCodes + { + return new PlacementLocationCodes(); + } + + public function placements(): Placements + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Placements(new Placement($this->core, $this->log)); + } + + return $this->serviceCache[__METHOD__]; + } + + public function eventV2(): EventV2 + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new EventV2($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + public function fileV2(): FileV2 + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new FileV2($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/IM/Message/Attach/Attach.php b/src/Services/IM/Message/Attach/Attach.php new file mode 100644 index 00000000..4e751348 --- /dev/null +++ b/src/Services/IM/Message/Attach/Attach.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach; + +use Bitrix24\SDK\Services\IM\Message\Attach\Blocks\DelimiterBlock; +use Bitrix24\SDK\Services\IM\Message\Attach\Blocks\MessageBlock; +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachPayloadInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken; + +final class Attach implements AttachPayloadInterface +{ + /** @var list */ + private array $blocks = []; + + private ?int $id = null; + + private ?AttachColorToken $colorToken = null; + + private ?string $color = null; + + public static function create(): self + { + return new self(); + } + + public function id(int $id): self + { + if ($id <= 0) { + throw new \InvalidArgumentException('Attach ID must be greater than zero'); + } + + $this->id = $id; + + return $this; + } + + public function colorToken(AttachColorToken $attachColorToken): self + { + $this->colorToken = $attachColorToken; + + return $this; + } + + public function color(string $hexColor): self + { + if (!$this->isValidHexColor($hexColor)) { + throw new \InvalidArgumentException('Attach color must be a valid hex color in #RGB or #RRGGBB format'); + } + + $this->color = $hexColor; + + return $this; + } + + public function add(AttachBlockInterface $attachBlock): self + { + $this->blocks[] = $attachBlock; + + return $this; + } + + public function message(string $text): self + { + return $this->add(MessageBlock::text($text)); + } + + public function delimiter(?int $size = null, ?string $color = null): self + { + $delimiterBlock = DelimiterBlock::create(); + + if ($size !== null) { + $delimiterBlock->size($size); + } + + if ($color !== null) { + $delimiterBlock->color($color); + } + + return $this->add($delimiterBlock); + } + + #[\Override] + public function build(): array + { + $blocks = array_map( + static fn (AttachBlockInterface $attachBlock): array => $attachBlock->build(), + $this->blocks + ); + + if ($this->id === null && !$this->colorToken instanceof \Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken && $this->color === null) { + return $blocks; + } + + return array_filter( + [ + 'ID' => $this->id, + 'COLOR_TOKEN' => $this->colorToken?->value, + 'COLOR' => $this->color, + 'BLOCKS' => $blocks, + ], + static fn (mixed $value): bool => $value !== null + ); + } + + private function isValidHexColor(string $hexColor): bool + { + return (bool)preg_match('/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $hexColor); + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/DelimiterBlock.php b/src/Services/IM/Message/Attach/Blocks/DelimiterBlock.php new file mode 100644 index 00000000..61131e3e --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/DelimiterBlock.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; + +final class DelimiterBlock implements AttachBlockInterface +{ + private ?int $size = null; + + private ?string $color = null; + + public static function create(): self + { + return new self(); + } + + public function size(int $size): self + { + if ($size <= 0) { + throw new \InvalidArgumentException('Delimiter size must be greater than zero'); + } + + $this->size = $size; + + return $this; + } + + public function color(string $hexColor): self + { + if (!$this->isValidHexColor($hexColor)) { + throw new \InvalidArgumentException('Delimiter color must be a valid hex color in #RGB or #RRGGBB format'); + } + + $this->color = $hexColor; + + return $this; + } + + private function isValidHexColor(string $hexColor): bool + { + return (bool)preg_match('/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $hexColor); + } + + #[\Override] + public function build(): array + { + return [ + 'DELIMITER' => array_filter( + [ + 'SIZE' => $this->size, + 'COLOR' => $this->color, + ], + static fn (mixed $value): bool => $value !== null + ), + ]; + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/FileBlock.php b/src/Services/IM/Message/Attach/Blocks/FileBlock.php new file mode 100644 index 00000000..ffa05a3d --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/FileBlock.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Items\FileItem; + +final class FileBlock implements AttachBlockInterface +{ + /** @var list */ + private array $items = []; + + public static function create(): self + { + return new self(); + } + + public function item(FileItem $fileItem): self + { + $this->items[] = $fileItem; + + return $this; + } + + #[\Override] + public function build(): array + { + if ($this->items === []) { + throw new \InvalidArgumentException('FILE block must contain at least one item'); + } + + return [ + 'FILE' => array_map( + static fn (FileItem $fileItem): array => $fileItem->build(), + $this->items + ), + ]; + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/GridBlock.php b/src/Services/IM/Message/Attach/Blocks/GridBlock.php new file mode 100644 index 00000000..78354fb9 --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/GridBlock.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken; +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\GridDisplay; +use Bitrix24\SDK\Services\IM\Message\Attach\Items\GridItem; + +final class GridBlock implements AttachBlockInterface +{ + /** @var list */ + private array $items = []; + + private ?int $width = null; + + private ?AttachColorToken $colorToken = null; + + private ?string $color = null; + + private function __construct( + private readonly GridDisplay $display, + ) { + } + + public static function display(GridDisplay $gridDisplay): self + { + return new self($gridDisplay); + } + + public function item(GridItem $gridItem): self + { + $this->items[] = $gridItem; + + return $this; + } + + public function width(int $width): self + { + if ($width <= 0) { + throw new \InvalidArgumentException('GRID width must be greater than zero'); + } + + $this->width = $width; + + return $this; + } + + public function colorToken(AttachColorToken $attachColorToken): self + { + $this->colorToken = $attachColorToken; + + return $this; + } + + public function color(string $hexColor): self + { + if (!$this->isValidHexColor($hexColor)) { + throw new \InvalidArgumentException('GRID color must be a valid hex color in #RGB or #RRGGBB format'); + } + + $this->color = $hexColor; + + return $this; + } + + #[\Override] + public function build(): array + { + if ($this->items === []) { + throw new \InvalidArgumentException('GRID block must contain at least one item'); + } + + return [ + 'GRID' => array_map( + fn (GridItem $gridItem): array => $this->buildItem($gridItem), + $this->items + ), + ]; + } + + private function buildItem(GridItem $gridItem): array + { + $builtItem = $gridItem->build(); + + if ($this->width !== null && !array_key_exists('WIDTH', $builtItem)) { + $builtItem['WIDTH'] = $this->width; + } + + if ($this->colorToken instanceof \Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken && !array_key_exists('COLOR_TOKEN', $builtItem)) { + $builtItem['COLOR_TOKEN'] = $this->colorToken->value; + } + + if ($this->color !== null && !array_key_exists('COLOR', $builtItem)) { + $builtItem['COLOR'] = $this->color; + } + + return $this->injectDisplay($builtItem); + } + + /** + * @param array $item + * + * @return array + */ + private function injectDisplay(array $item): array + { + $withDisplay = []; + + foreach (['NAME', 'VALUE'] as $key) { + if (array_key_exists($key, $item)) { + $withDisplay[$key] = $item[$key]; + unset($item[$key]); + } + } + + $withDisplay['DISPLAY'] = $this->display->value; + + return $withDisplay + $item; + } + + private function isValidHexColor(string $hexColor): bool + { + return (bool) preg_match('/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $hexColor); + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/ImageBlock.php b/src/Services/IM/Message/Attach/Blocks/ImageBlock.php new file mode 100644 index 00000000..4d129787 --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/ImageBlock.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Items\ImageItem; + +final class ImageBlock implements AttachBlockInterface +{ + /** @var list */ + private array $items = []; + + public static function create(): self + { + return new self(); + } + + public function item(ImageItem $imageItem): self + { + $this->items[] = $imageItem; + + return $this; + } + + #[\Override] + public function build(): array + { + if ($this->items === []) { + throw new \InvalidArgumentException('IMAGE block must contain at least one item'); + } + + return [ + 'IMAGE' => array_map( + static fn (ImageItem $imageItem): array => $imageItem->build(), + $this->items + ), + ]; + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/LinkBlock.php b/src/Services/IM/Message/Attach/Blocks/LinkBlock.php new file mode 100644 index 00000000..423a40f8 --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/LinkBlock.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; + +final class LinkBlock implements AttachBlockInterface +{ + private const string TARGET_URL = 'url'; + + private const string TARGET_USER = 'user'; + + private const string TARGET_CHAT = 'chat'; + + private const string TARGET_NETWORK = 'network'; + + private ?string $link = null; + + private ?string $targetMode = null; + + private ?string $name = null; + + private ?string $description = null; + + private ?string $html = null; + + private ?string $preview = null; + + private ?int $width = null; + + private ?int $height = null; + + private ?int $userId = null; + + private ?int $chatId = null; + + private ?string $networkId = null; + + public static function url(string $link): self + { + $block = new self(); + $block->setTargetMode(self::TARGET_URL); + + $block->link = self::requireNonEmptyString($link, 'LINK'); + + return $block; + } + + public static function user(int $userId, ?string $name = null): self + { + $block = new self(); + $block->setTargetMode(self::TARGET_USER); + $block->userId($userId); + + if ($name !== null) { + $block->name($name); + } + + return $block; + } + + public static function chat(int $chatId, ?string $name = null): self + { + $block = new self(); + $block->setTargetMode(self::TARGET_CHAT); + $block->chatId($chatId); + + if ($name !== null) { + $block->name($name); + } + + return $block; + } + + public static function network(string $networkId, ?string $name = null): self + { + $block = new self(); + $block->setTargetMode(self::TARGET_NETWORK); + $block->networkId($networkId); + + if ($name !== null) { + $block->name($name); + } + + return $block; + } + + public function name(string $name): self + { + $this->name = self::requireNonEmptyString($name, 'NAME'); + + return $this; + } + + public function description(string $description): self + { + $this->description = self::requireNonEmptyString($description, 'DESC'); + + return $this; + } + + public function html(string $html): self + { + $this->html = self::requireNonEmptyString($html, 'HTML'); + + return $this; + } + + public function preview(string $url): self + { + $this->preview = self::requireNonEmptyString($url, 'PREVIEW'); + + return $this; + } + + public function width(int $width): self + { + if ($width <= 0) { + throw new \InvalidArgumentException('LINK width must be greater than zero'); + } + + $this->width = $width; + + return $this; + } + + public function height(int $height): self + { + if ($height <= 0) { + throw new \InvalidArgumentException('LINK height must be greater than zero'); + } + + $this->height = $height; + + return $this; + } + + public function userId(int $userId): self + { + $this->setTargetMode(self::TARGET_USER); + + if ($userId <= 0) { + throw new \InvalidArgumentException('USER_ID must be greater than zero'); + } + + $this->userId = $userId; + + return $this; + } + + public function chatId(int $chatId): self + { + $this->setTargetMode(self::TARGET_CHAT); + + if ($chatId <= 0) { + throw new \InvalidArgumentException('CHAT_ID must be greater than zero'); + } + + $this->chatId = $chatId; + + return $this; + } + + public function networkId(string $networkId): self + { + $this->setTargetMode(self::TARGET_NETWORK); + $this->networkId = self::requireNonEmptyString($networkId, 'NETWORK_ID'); + + return $this; + } + + #[\Override] + public function build(): array + { + $fields = []; + + match ($this->targetMode) { + self::TARGET_URL => $fields['LINK'] = $this->link, + self::TARGET_USER => $fields['USER_ID'] = $this->userId, + self::TARGET_CHAT => $fields['CHAT_ID'] = $this->chatId, + self::TARGET_NETWORK => $fields['NETWORK_ID'] = $this->networkId, + default => throw new \LogicException('Link target mode is not configured'), + }; + + $this->appendField($fields, 'NAME', $this->name); + $this->appendField($fields, 'DESC', $this->description); + $this->appendField($fields, 'HTML', $this->html); + $this->appendField($fields, 'PREVIEW', $this->preview); + $this->appendField($fields, 'WIDTH', $this->width); + $this->appendField($fields, 'HEIGHT', $this->height); + + return [ + 'LINK' => array_filter( + $fields, + static fn (mixed $value): bool => $value !== null + ), + ]; + } + + private static function requireNonEmptyString(string $value, string $fieldName): string + { + if ($value === '') { + throw new \InvalidArgumentException(sprintf('%s must not be empty', $fieldName)); + } + + return $value; + } + + /** + * @param array $fields + */ + private function appendField(array &$fields, string $key, int|string|null $value): void + { + if ($value !== null) { + $fields[$key] = $value; + } + } + + private function setTargetMode(string $targetMode): void + { + if ($this->targetMode !== null && $this->targetMode !== $targetMode) { + throw new \InvalidArgumentException('LinkBlock can only use one target mode'); + } + + $this->targetMode = $targetMode; + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/MessageBlock.php b/src/Services/IM/Message/Attach/Blocks/MessageBlock.php new file mode 100644 index 00000000..9e543f55 --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/MessageBlock.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; + +final readonly class MessageBlock implements AttachBlockInterface +{ + private function __construct( + private string $text, + ) { + if ($text === '') { + throw new \InvalidArgumentException('Message text must not be empty'); + } + } + + public static function text(string $text): self + { + return new self($text); + } + + #[\Override] + public function build(): array + { + return [ + 'MESSAGE' => $this->text, + ]; + } +} diff --git a/src/Services/IM/Message/Attach/Blocks/UserBlock.php b/src/Services/IM/Message/Attach/Blocks/UserBlock.php new file mode 100644 index 00000000..78cdedb4 --- /dev/null +++ b/src/Services/IM/Message/Attach/Blocks/UserBlock.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Blocks; + +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachAvatarType; +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachBlockInterface; + +final class UserBlock implements AttachBlockInterface +{ + private ?string $name = null; + + private ?int $userId = null; + + private ?int $chatId = null; + + private ?int $botId = null; + + private ?string $networkId = null; + + private ?string $avatar = null; + + private ?string $link = null; + + private ?AttachAvatarType $avatarType = null; + + public static function name(string $name): self + { + $block = new self(); + $block->name = self::requireNonEmptyString($name, 'NAME'); + + return $block; + } + + public function userId(int $userId): self + { + if ($userId <= 0) { + throw new \InvalidArgumentException('USER_ID must be greater than zero'); + } + + $this->userId = $userId; + + return $this; + } + + public function chatId(int $chatId): self + { + if ($chatId <= 0) { + throw new \InvalidArgumentException('CHAT_ID must be greater than zero'); + } + + $this->chatId = $chatId; + + return $this; + } + + public function botId(int $botId): self + { + if ($botId <= 0) { + throw new \InvalidArgumentException('BOT_ID must be greater than zero'); + } + + $this->botId = $botId; + + return $this; + } + + public function networkId(string $networkId): self + { + $this->networkId = self::requireNonEmptyString($networkId, 'NETWORK_ID'); + + return $this; + } + + public function avatar(string $url): self + { + $this->avatar = self::requireNonEmptyString($url, 'AVATAR'); + + return $this; + } + + public function link(string $url): self + { + $this->link = self::requireNonEmptyString($url, 'LINK'); + + return $this; + } + + public function avatarType(AttachAvatarType $attachAvatarType): self + { + $this->avatarType = $attachAvatarType; + + return $this; + } + + public function avatarTypeUser(): self + { + return $this->avatarType(AttachAvatarType::user); + } + + public function avatarTypeChat(): self + { + return $this->avatarType(AttachAvatarType::chat); + } + + public function avatarTypeBot(): self + { + return $this->avatarType(AttachAvatarType::bot); + } + + #[\Override] + public function build(): array + { + return [ + 'USER' => array_filter( + [ + 'NAME' => $this->name, + 'USER_ID' => $this->userId, + 'CHAT_ID' => $this->chatId, + 'BOT_ID' => $this->botId, + 'NETWORK_ID' => $this->networkId, + 'AVATAR' => $this->avatar, + 'LINK' => $this->link, + 'AVATAR_TYPE' => $this->avatarType?->value, + ], + static fn (mixed $value): bool => $value !== null + ), + ]; + } + + private static function requireNonEmptyString(string $value, string $fieldName): string + { + if ($value === '') { + throw new \InvalidArgumentException(sprintf('%s must not be empty', $fieldName)); + } + + return $value; + } +} diff --git a/src/Services/IM/Message/Attach/Contracts/AttachBlockInterface.php b/src/Services/IM/Message/Attach/Contracts/AttachBlockInterface.php new file mode 100644 index 00000000..e1d1a2ef --- /dev/null +++ b/src/Services/IM/Message/Attach/Contracts/AttachBlockInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Contracts; + +interface AttachBlockInterface +{ + public function build(): array; +} diff --git a/src/Services/IM/Message/Attach/Contracts/AttachItemInterface.php b/src/Services/IM/Message/Attach/Contracts/AttachItemInterface.php new file mode 100644 index 00000000..eb8e7c1a --- /dev/null +++ b/src/Services/IM/Message/Attach/Contracts/AttachItemInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Contracts; + +interface AttachItemInterface +{ + public function build(): array; +} diff --git a/src/Services/IM/Message/Attach/Contracts/AttachPayloadInterface.php b/src/Services/IM/Message/Attach/Contracts/AttachPayloadInterface.php new file mode 100644 index 00000000..a1875c70 --- /dev/null +++ b/src/Services/IM/Message/Attach/Contracts/AttachPayloadInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Contracts; + +interface AttachPayloadInterface +{ + public function build(): array; +} diff --git a/src/Services/IM/Message/Attach/Enums/AttachAvatarType.php b/src/Services/IM/Message/Attach/Enums/AttachAvatarType.php new file mode 100644 index 00000000..95b8e14a --- /dev/null +++ b/src/Services/IM/Message/Attach/Enums/AttachAvatarType.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Enums; + +enum AttachAvatarType: string +{ + case user = 'USER'; + case chat = 'CHAT'; + case bot = 'BOT'; +} diff --git a/src/Services/IM/Message/Attach/Enums/AttachColorToken.php b/src/Services/IM/Message/Attach/Enums/AttachColorToken.php new file mode 100644 index 00000000..a8242a0e --- /dev/null +++ b/src/Services/IM/Message/Attach/Enums/AttachColorToken.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Enums; + +enum AttachColorToken: string +{ + case primary = 'primary'; + case secondary = 'secondary'; + case alert = 'alert'; + case base = 'base'; +} diff --git a/src/Services/IM/Message/Attach/Enums/GridDisplay.php b/src/Services/IM/Message/Attach/Enums/GridDisplay.php new file mode 100644 index 00000000..a9ad9099 --- /dev/null +++ b/src/Services/IM/Message/Attach/Enums/GridDisplay.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Enums; + +enum GridDisplay: string +{ + case block = 'BLOCK'; + case line = 'LINE'; + case row = 'ROW'; + case table = 'TABLE'; +} diff --git a/src/Services/IM/Message/Attach/Items/FileItem.php b/src/Services/IM/Message/Attach/Items/FileItem.php new file mode 100644 index 00000000..546972e0 --- /dev/null +++ b/src/Services/IM/Message/Attach/Items/FileItem.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Items; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachItemInterface; + +final class FileItem implements AttachItemInterface +{ + private readonly string $link; + + private ?string $name = null; + + private ?int $size = null; + + private function __construct(string $link) + { + $this->link = $this->requireNonEmptyString($link, 'LINK'); + } + + public static function link(string $link): self + { + return new self($link); + } + + public function name(string $name): self + { + $this->name = $this->requireNonEmptyString($name, 'NAME'); + + return $this; + } + + public function size(int $bytes): self + { + if ($bytes <= 0) { + throw new \InvalidArgumentException('FILE size must be greater than zero'); + } + + $this->size = $bytes; + + return $this; + } + + #[\Override] + public function build(): array + { + return array_filter( + [ + 'LINK' => $this->link, + 'NAME' => $this->name, + 'SIZE' => $this->size, + ], + static fn (mixed $value): bool => $value !== null + ); + } + + private function requireNonEmptyString(string $value, string $fieldName): string + { + if ($value === '') { + throw new \InvalidArgumentException(sprintf('%s must not be empty', $fieldName)); + } + + return $value; + } +} diff --git a/src/Services/IM/Message/Attach/Items/GridItem.php b/src/Services/IM/Message/Attach/Items/GridItem.php new file mode 100644 index 00000000..efcf9f3b --- /dev/null +++ b/src/Services/IM/Message/Attach/Items/GridItem.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Items; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachItemInterface; +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken; + +final class GridItem implements AttachItemInterface +{ + private readonly string $name; + + private ?string $value = null; + + private ?string $link = null; + + private ?int $userId = null; + + private ?int $chatId = null; + + private ?int $width = null; + + private ?int $height = null; + + private ?AttachColorToken $colorToken = null; + + private ?string $color = null; + + private function __construct(string $name) + { + $this->name = $this->requireNonEmptyString($name, 'NAME'); + } + + public static function name(string $name): self + { + return new self($name); + } + + public function value(string $value): self + { + $this->value = $this->requireNonEmptyString($value, 'VALUE'); + + return $this; + } + + public function link(string $url): self + { + $this->link = $this->requireNonEmptyString($url, 'LINK'); + + return $this; + } + + public function userId(int $userId): self + { + if ($userId <= 0) { + throw new \InvalidArgumentException('USER_ID must be greater than zero'); + } + + $this->userId = $userId; + + return $this; + } + + public function chatId(int $chatId): self + { + if ($chatId <= 0) { + throw new \InvalidArgumentException('CHAT_ID must be greater than zero'); + } + + $this->chatId = $chatId; + + return $this; + } + + public function width(int $width): self + { + if ($width <= 0) { + throw new \InvalidArgumentException('GRID width must be greater than zero'); + } + + $this->width = $width; + + return $this; + } + + public function height(int $height): self + { + if ($height <= 0) { + throw new \InvalidArgumentException('GRID height must be greater than zero'); + } + + $this->height = $height; + + return $this; + } + + public function colorToken(AttachColorToken $attachColorToken): self + { + $this->colorToken = $attachColorToken; + + return $this; + } + + public function color(string $hexColor): self + { + if (!$this->isValidHexColor($hexColor)) { + throw new \InvalidArgumentException('GRID color must be a valid hex color in #RGB or #RRGGBB format'); + } + + $this->color = $hexColor; + + return $this; + } + + #[\Override] + public function build(): array + { + return array_filter( + [ + 'NAME' => $this->name, + 'VALUE' => $this->value, + 'LINK' => $this->link, + 'USER_ID' => $this->userId, + 'CHAT_ID' => $this->chatId, + 'WIDTH' => $this->width, + 'HEIGHT' => $this->height, + 'COLOR_TOKEN' => $this->colorToken?->value, + 'COLOR' => $this->color, + ], + static fn (mixed $value): bool => $value !== null + ); + } + + private function requireNonEmptyString(string $value, string $fieldName): string + { + if ($value === '') { + throw new \InvalidArgumentException(sprintf('%s must not be empty', $fieldName)); + } + + return $value; + } + + private function isValidHexColor(string $hexColor): bool + { + return (bool) preg_match('/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $hexColor); + } +} diff --git a/src/Services/IM/Message/Attach/Items/ImageItem.php b/src/Services/IM/Message/Attach/Items/ImageItem.php new file mode 100644 index 00000000..b068abfb --- /dev/null +++ b/src/Services/IM/Message/Attach/Items/ImageItem.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach\Items; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachItemInterface; + +final class ImageItem implements AttachItemInterface +{ + private readonly string $link; + + private ?string $name = null; + + private ?string $preview = null; + + private ?int $width = null; + + private ?int $height = null; + + private function __construct(string $link) + { + $this->link = $this->requireNonEmptyString($link, 'LINK'); + } + + public static function link(string $link): self + { + return new self($link); + } + + public function name(string $name): self + { + $this->name = $this->requireNonEmptyString($name, 'NAME'); + + return $this; + } + + public function preview(string $url): self + { + $this->preview = $this->requireNonEmptyString($url, 'PREVIEW'); + + return $this; + } + + public function width(int $width): self + { + if ($width <= 0) { + throw new \InvalidArgumentException('IMAGE width must be greater than zero'); + } + + $this->width = $width; + + return $this; + } + + public function height(int $height): self + { + if ($height <= 0) { + throw new \InvalidArgumentException('IMAGE height must be greater than zero'); + } + + $this->height = $height; + + return $this; + } + + #[\Override] + public function build(): array + { + return array_filter( + [ + 'LINK' => $this->link, + 'NAME' => $this->name, + 'PREVIEW' => $this->preview, + 'WIDTH' => $this->width, + 'HEIGHT' => $this->height, + ], + static fn (mixed $value): bool => $value !== null + ); + } + + private function requireNonEmptyString(string $value, string $fieldName): string + { + if ($value === '') { + throw new \InvalidArgumentException(sprintf('%s must not be empty', $fieldName)); + } + + return $value; + } +} diff --git a/src/Services/IM/Message/Attach/RawAttach.php b/src/Services/IM/Message/Attach/RawAttach.php new file mode 100644 index 00000000..973c2deb --- /dev/null +++ b/src/Services/IM/Message/Attach/RawAttach.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Attach; + +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachPayloadInterface; + +final readonly class RawAttach implements AttachPayloadInterface +{ + /** + * @param array $payload + */ + private function __construct( + private array $payload, + ) { + } + + /** + * @param array $payload + */ + public static function fromArray(array $payload): self + { + return new self($payload); + } + + #[\Override] + public function build(): array + { + return $this->payload; + } +} diff --git a/src/Services/IM/Message/Service/LikeAction.php b/src/Services/IM/Message/Service/LikeAction.php new file mode 100644 index 00000000..9439f4ca --- /dev/null +++ b/src/Services/IM/Message/Service/LikeAction.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Service; + +enum LikeAction: string +{ + case auto = 'auto'; + case plus = 'plus'; + case minus = 'minus'; +} diff --git a/src/Services/IM/Message/Service/Message.php b/src/Services/IM/Message/Service/Message.php new file mode 100644 index 00000000..a1a8849f --- /dev/null +++ b/src/Services/IM/Message/Service/Message.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\AddedItemResult; +use Bitrix24\SDK\Core\Result\DeletedItemResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachPayloadInterface; +use Bitrix24\SDK\Services\AbstractService; + +#[ApiServiceMetadata(new Scope(['im']))] +class Message extends AbstractService +{ + /** + * @param array|string|AttachPayloadInterface|null $attach + * Raw JSON string payloads are deprecated; prefer AttachPayloadInterface for typed construction + * or array payloads for backward-compatible raw structures. + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.add', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-add.html', + 'Send a message to a chat' + )] + public function add( + string $dialogId, + ?string $message = null, + array|string|AttachPayloadInterface|null $attach = null, + array|string|null $keyboard = null, + array|string|null $menu = null, + bool $isSystem = false, + bool $urlPreview = true, + ?int $replyId = null, + ): AddedItemResult { + return new AddedItemResult($this->core->call( + 'im.message.add', + [ + 'DIALOG_ID' => $dialogId, + 'MESSAGE' => $message, + 'ATTACH' => $this->normalizeAttach($attach), + 'KEYBOARD' => $keyboard, + 'MENU' => $menu, + 'SYSTEM' => $isSystem ? 'Y' : 'N', + 'URL_PREVIEW' => $urlPreview ? 'Y' : 'N', + 'REPLY_ID' => $replyId, + ] + )); + } + + /** + * @param array|string|AttachPayloadInterface|null $attach + * Raw JSON string payloads are deprecated; prefer AttachPayloadInterface for typed construction + * or array payloads for backward-compatible raw structures. + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.update', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-update.html', + 'Update text and parameters of a sent message' + )] + public function update( + int $messageId, + ?string $message = null, + array|string|AttachPayloadInterface|null $attach = null, + array|string|null $keyboard = null, + array|string|null $menu = null, + ?bool $urlPreview = null, + ?bool $isEdited = null, + ): UpdatedItemResult { + return new UpdatedItemResult($this->core->call( + 'im.message.update', + [ + 'MESSAGE_ID' => $messageId, + 'MESSAGE' => $message, + 'ATTACH' => $this->normalizeAttach($attach), + 'KEYBOARD' => $keyboard, + 'MENU' => $menu, + 'URL_PREVIEW' => $urlPreview === null ? null : ($urlPreview ? 'Y' : 'N'), + 'IS_EDITED' => $isEdited === null ? null : ($isEdited ? 'Y' : 'N'), + ] + )); + } + + /** + * @param array|string|AttachPayloadInterface|null $attach + * Raw JSON string payloads are deprecated and remain supported only for backward compatibility. + * + * @return array|string|null + */ + private function normalizeAttach(array|string|AttachPayloadInterface|null $attach): array|string|null + { + if ($attach instanceof AttachPayloadInterface) { + return $attach->build(); + } + + return $attach; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.delete', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-delete.html', + 'Delete a message' + )] + public function delete(int $messageId): DeletedItemResult + { + return new DeletedItemResult($this->core->call( + 'im.message.delete', + ['MESSAGE_ID' => $messageId] + )); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.like', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-like.html', + 'Toggle the "Like" mark on a message' + )] + public function like(int $messageId, LikeAction $likeAction = LikeAction::auto): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call( + 'im.message.like', + [ + 'MESSAGE_ID' => $messageId, + 'ACTION' => $likeAction->value, + ] + )); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.share', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-share.html', + 'Create an object (chat/task/post/calendar event) based on a message' + )] + public function share(int $messageId, string $dialogId, ShareType $shareType): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call( + 'im.message.share', + [ + 'MESSAGE_ID' => $messageId, + 'DIALOG_ID' => $dialogId, + 'TYPE' => $shareType->value, + ] + )); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.message.command', + 'https://apidocs.bitrix24.com/api-reference/chats/messages/im-message-command.html', + 'Invoke a chat-bot command in the context of a message' + )] + public function command( + int $messageId, + int $botId, + string $command, + ?string $commandParams = null, + ): UpdatedItemResult { + return new UpdatedItemResult($this->core->call( + 'im.message.command', + [ + 'MESSAGE_ID' => $messageId, + 'BOT_ID' => $botId, + 'COMMAND' => $command, + 'COMMAND_PARAMS' => $commandParams, + ] + )); + } +} diff --git a/src/Services/IM/Message/Service/ShareType.php b/src/Services/IM/Message/Service/ShareType.php new file mode 100644 index 00000000..87f9c57f --- /dev/null +++ b/src/Services/IM/Message/Service/ShareType.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Message\Service; + +enum ShareType: string +{ + case chat = 'CHAT'; + case task = 'TASK'; + case post = 'POST'; + case calendarEvent = 'CALEND'; +} diff --git a/src/Services/IM/Notify/Result/NotifiesResult.php b/src/Services/IM/Notify/Result/NotifiesResult.php new file mode 100644 index 00000000..626a919d --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifiesResult.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NotifiesResult extends AbstractResult +{ + /** + * @return NotifyItemResult[] + * @throws BaseException + */ + public function notifications(): array + { + return array_map( + static fn (array $item): NotifyItemResult => new NotifyItemResult($item), + array_filter($this->payload()['notifications'] ?? [], 'is_array') + ); + } + + /** + * @throws BaseException + */ + public function totalCount(): int + { + return (int)($this->payload()['total_count'] ?? 0); + } + + /** + * @throws BaseException + */ + public function totalUnreadCount(): int + { + return (int)($this->payload()['total_unread_count'] ?? 0); + } + + /** + * @throws BaseException + */ + public function chatId(): int + { + return (int)($this->payload()['chat_id'] ?? 0); + } + + /** + * @return array> + * @throws BaseException + */ + public function users(): array + { + return array_values(array_filter($this->payload()['users'] ?? [], 'is_array')); + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_is_list($result) && is_array($result[0] ?? null) ? $result[0] : $result; + } +} diff --git a/src/Services/IM/Notify/Result/NotifyHistorySearchResult.php b/src/Services/IM/Notify/Result/NotifyHistorySearchResult.php new file mode 100644 index 00000000..01b894c3 --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifyHistorySearchResult.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NotifyHistorySearchResult extends AbstractResult +{ + /** + * @return NotifyItemResult[] + * @throws BaseException + */ + public function notifications(): array + { + return array_map( + static fn (array $item): NotifyItemResult => new NotifyItemResult($item), + array_filter($this->payload()['notifications'] ?? [], 'is_array') + ); + } + + /** + * @throws BaseException + */ + public function totalResults(): int + { + return (int)($this->payload()['total_results'] ?? 0); + } + + /** + * @throws BaseException + */ + public function chatId(): int + { + return (int)($this->payload()['chat_id'] ?? 0); + } + + /** + * @return array> + * @throws BaseException + */ + public function users(): array + { + return array_values(array_filter($this->payload()['users'] ?? [], 'is_array')); + } + + /** + * @return array + * @throws BaseException + */ + private function payload(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_is_list($result) && is_array($result[0] ?? null) ? $result[0] : $result; + } +} diff --git a/src/Services/IM/Notify/Result/NotifyItemResult.php b/src/Services/IM/Notify/Result/NotifyItemResult.php new file mode 100644 index 00000000..fe7a78e7 --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifyItemResult.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read int $chat_id + * @property-read int $author_id + * @property-read CarbonImmutable $date + * @property-read int $notify_type + * @property-read string $notify_module + * @property-read string $notify_event + * @property-read string $notify_tag + * @property-read string $notify_sub_tag + * @property-read string $notify_title + * @property-read string $setting_name + * @property-read string $text + * @property-read string $notify_read + * @property-read array|null $params + */ +class NotifyItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Notify/Result/NotifyReadAllResult.php b/src/Services/IM/Notify/Result/NotifyReadAllResult.php new file mode 100644 index 00000000..d93228a9 --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifyReadAllResult.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NotifyReadAllResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()['result'] ?? false); + } + + /** + * @throws BaseException + */ + public function newCounter(): int + { + return (int)($this->getCoreResponse()->getResponseData()->getResult()['newCounter'] ?? 0); + } +} diff --git a/src/Services/IM/Notify/Result/NotifySchemaItemResult.php b/src/Services/IM/Notify/Result/NotifySchemaItemResult.php new file mode 100644 index 00000000..12bb64a2 --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifySchemaItemResult.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read string $NAME + * @property-read string $MODULE_ID + * @property-read array $LIST + */ +class NotifySchemaItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Notify/Result/NotifySchemaResult.php b/src/Services/IM/Notify/Result/NotifySchemaResult.php new file mode 100644 index 00000000..00eaa5fe --- /dev/null +++ b/src/Services/IM/Notify/Result/NotifySchemaResult.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class NotifySchemaResult extends AbstractResult +{ + /** + * @return NotifySchemaItemResult[] + * @throws BaseException + */ + public function schema(): array + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + + return array_map( + static fn (array $module): NotifySchemaItemResult => new NotifySchemaItemResult($module), + array_values(array_filter($result, 'is_array')) + ); + } +} diff --git a/src/Services/IM/Notify/Service/Notify.php b/src/Services/IM/Notify/Service/Notify.php index 27deffa7..918ea63f 100644 --- a/src/Services/IM/Notify/Service/Notify.php +++ b/src/Services/IM/Notify/Service/Notify.php @@ -13,7 +13,6 @@ namespace Bitrix24\SDK\Services\IM\Notify\Service; - use Bitrix24\SDK\Attributes\ApiEndpointMetadata; use Bitrix24\SDK\Attributes\ApiServiceMetadata; use Bitrix24\SDK\Core\Credentials\Scope; @@ -23,6 +22,11 @@ use Bitrix24\SDK\Core\Result\DeletedItemResult; use Bitrix24\SDK\Core\Result\UpdatedItemResult; use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifiesResult; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifyHistorySearchResult; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifyReadAllResult; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifySchemaResult; +use Carbon\CarbonImmutable; #[ApiServiceMetadata(new Scope(['im']))] class Notify extends AbstractService @@ -38,7 +42,7 @@ class Notify extends AbstractService */ #[ApiEndpointMetadata( 'im.notify.system.add', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23904&LESSON_PATH=9691.9805.11585.23904', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-system-add.html', 'Sending system notification' )] public function fromSystem( @@ -48,8 +52,7 @@ public function fromSystem( ?string $notificationTag = null, ?string $subTag = null, ?array $attachment = null - ): AddedItemResult - { + ): AddedItemResult { return new AddedItemResult($this->core->call( 'im.notify.system.add', [ @@ -63,9 +66,18 @@ public function fromSystem( )); } + /** + * @param positive-int $userId + * @param non-empty-string $message + * @param non-empty-string|null $forEmailChannelMessage + * @param non-empty-string|null $notificationTag + * @param non-empty-string|null $subTag + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( 'im.notify.personal.add', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23904&LESSON_PATH=9691.9805.11585.23904', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-personal-add.html', 'Sending personal notification' )] public function fromPersonal( @@ -75,8 +87,7 @@ public function fromPersonal( ?string $notificationTag = null, ?string $subTag = null, ?array $attachment = null - ): AddedItemResult - { + ): AddedItemResult { return new AddedItemResult($this->core->call( 'im.notify.personal.add', [ @@ -90,17 +101,52 @@ public function fromPersonal( )); } + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.notify', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify.html', + 'Send a notification from application context' + )] + public function send( + int $userId, + string $message, + string $type = 'USER', + ?string $forEmailChannelMessage = null, + ?string $notificationTag = null, + ?string $subTag = null, + ?array $attachment = null + ): AddedItemResult { + return new AddedItemResult($this->core->call( + 'im.notify', + [ + 'USER_ID' => $userId, + 'MESSAGE' => $message, + 'TYPE' => $type, + 'MESSAGE_OUT' => $forEmailChannelMessage, + 'TAG' => $notificationTag, + 'SUB_TAG' => $subTag, + 'ATTACH' => $attachment, + ] + )); + } + + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( 'im.notify.delete', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23906&LESSON_PATH=9691.9805.11585.23906', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-delete.html', 'Deleting notification' )] public function delete( int $notificationId, ?string $notificationTag = null, ?string $subTag = null, - ): DeletedItemResult - { + ): DeletedItemResult { return new DeletedItemResult($this->core->call( 'im.notify.delete', [ @@ -111,16 +157,19 @@ public function delete( )); } + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( 'im.notify.read', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=11587&LESSON_PATH=9691.9805.11585.11587', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-read.html', 'The method cancels notification for read messages.' )] public function markAsRead( int $notificationId, bool $isOnlyCurrent = true, - ): UpdatedItemResult - { + ): UpdatedItemResult { return new UpdatedItemResult($this->core->call( 'im.notify.read', [ @@ -130,17 +179,20 @@ public function markAsRead( )); } + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( - 'im.notify.read', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23908&LESSON_PATH=9691.9805.11585.23908', + 'im.notify.read.list', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-read-list.html', '"Read" the list of notifications, excluding CONFIRM notification type' )] public function markMessagesAsRead( array $notificationIds - ): UpdatedItemResult - { + ): UpdatedItemResult { return new UpdatedItemResult($this->core->call( - 'im.notify.read', + 'im.notify.read.list', [ 'IDS' => $notificationIds, 'ACTION' => 'Y', @@ -148,17 +200,20 @@ public function markMessagesAsRead( )); } + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( - 'im.notify.read', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23908&LESSON_PATH=9691.9805.11585.23908', + 'im.notify.read.list', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-read-list.html', '"Unread" the list of notifications, excluding CONFIRM notification type' )] public function markMessagesAsUnread( array $notificationIds - ): UpdatedItemResult - { + ): UpdatedItemResult { return new UpdatedItemResult($this->core->call( - 'im.notify.read', + 'im.notify.read.list', [ 'IDS' => $notificationIds, 'ACTION' => 'N', @@ -166,16 +221,33 @@ public function markMessagesAsUnread( )); } + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.notify.read.all', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-read-all.html', + 'Mark all notifications as read' + )] + public function markAllAsRead(): NotifyReadAllResult + { + return new NotifyReadAllResult($this->core->call('im.notify.read.all', [])); + } + + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( 'im.notify.confirm', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23912&LESSON_PATH=9691.9805.11585.23912', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-confirm.html', 'Interaction with notification buttons' )] public function confirm( int $notificationId, bool $isAccept - ): UpdatedItemResult - { + ): UpdatedItemResult { return new UpdatedItemResult($this->core->call( 'im.notify.confirm', [ @@ -185,16 +257,19 @@ public function confirm( )); } + /** + * @throws BaseException + * @throws TransportException + */ #[ApiEndpointMetadata( 'im.notify.answer', - 'https://training.bitrix24.com/support/training/course/index.php?COURSE_ID=115&LESSON_ID=23910&LESSON_PATH=9691.9805.11585.23910', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-answer.html', 'Response to notification, supporting quick reply' )] public function answer( int $notificationId, string $answerText - ): UpdatedItemResult - { + ): UpdatedItemResult { return new UpdatedItemResult($this->core->call( 'im.notify.answer', [ @@ -203,4 +278,82 @@ public function answer( ] )); } -} \ No newline at end of file + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.notify.get', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-get.html', + 'Get list of user notifications paginated by LAST_ID and LAST_TYPE' + )] + public function getList( + ?int $lastId = null, + ?int $lastType = null, + int $limit = 50, + ): NotifiesResult { + $params = array_filter( + [ + 'LAST_ID' => $lastId, + 'LAST_TYPE' => $lastType, + 'LIMIT' => $limit, + ], + static fn (mixed $value): bool => $value !== null + ); + $params['LIMIT'] = $limit; + + return new NotifiesResult($this->core->call('im.notify.get', $params)); + } + + /** + * @param non-empty-string[]|null $searchTypes + * @param positive-int[]|null $searchAuthors + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.notify.history.search', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-history-search.html', + 'Search notification history' + )] + public function historySearch( + ?string $searchText = null, + ?array $searchTypes = null, + ?CarbonImmutable $searchDateFrom = null, + ?CarbonImmutable $searchDateTo = null, + ?array $searchAuthors = null, + ?int $lastId = null, + int $limit = 50, + ): NotifyHistorySearchResult { + $params = array_filter( + [ + 'SEARCH_TEXT' => $searchText, + 'SEARCH_TYPES' => $searchTypes, + 'SEARCH_DATE_FROM' => $searchDateFrom?->toIso8601String(), + 'SEARCH_DATE_TO' => $searchDateTo?->toIso8601String(), + 'SEARCH_AUTHORS' => $searchAuthors, + 'LAST_ID' => $lastId, + 'LIMIT' => $limit, + ], + static fn (mixed $value): bool => $value !== null + ); + $params['LIMIT'] = $limit; + + return new NotifyHistorySearchResult($this->core->call('im.notify.history.search', $params)); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.notify.schema.get', + 'https://apidocs.bitrix24.com/api-reference/chats/notifications/im-notify-schema-get.html', + 'Get schema of available notification types per module' + )] + public function getSchema(): NotifySchemaResult + { + return new NotifySchemaResult($this->core->call('im.notify.schema.get', [])); + } +} diff --git a/src/Services/IM/Placements/AbstractPlacementOptions.php b/src/Services/IM/Placements/AbstractPlacementOptions.php new file mode 100644 index 00000000..b3e90ffd --- /dev/null +++ b/src/Services/IM/Placements/AbstractPlacementOptions.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +use Bitrix24\SDK\Services\Placement\ExtranetAvailability; +use Bitrix24\SDK\Services\Placement\PlacementOptionsInterface; +use Bitrix24\SDK\Services\Placement\Role; + +abstract class AbstractPlacementOptions implements PlacementOptionsInterface +{ + /** @var array */ + protected array $fields = []; + + public function context(ChatContext ...$chatContext): static + { + $this->fields['context'] = implode(';', array_map( + static fn (ChatContext $chatContext): string => $chatContext->value, + $chatContext, + )); + + return $this; + } + + public function role(Role $role): static + { + $this->fields['role'] = $role->value; + + return $this; + } + + public function extranet(ExtranetAvailability $extranetAvailability): static + { + $this->fields['extranet'] = $extranetAvailability->value; + + return $this; + } + + #[\Override] + public function build(): array + { + return $this->fields; + } +} diff --git a/src/Services/IM/Placements/ChatContext.php b/src/Services/IM/Placements/ChatContext.php new file mode 100644 index 00000000..c310e1b6 --- /dev/null +++ b/src/Services/IM/Placements/ChatContext.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Chat type for which an IM widget is bound. + * + * Multi-value parameter: pass several cases to a placement options builder + * and they are joined with `;` for the API. + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ +enum ChatContext: string +{ + case USER = 'USER'; + case CHAT = 'CHAT'; + case LINES = 'LINES'; + case CRM = 'CRM'; + case ALL = 'ALL'; +} diff --git a/src/Services/IM/Placements/ImContextMenuPlacementOptions.php b/src/Services/IM/Placements/ImContextMenuPlacementOptions.php new file mode 100644 index 00000000..1faa9472 --- /dev/null +++ b/src/Services/IM/Placements/ImContextMenuPlacementOptions.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Fluent options builder for the `IM_CONTEXT_MENU` placement + * (context-menu item on a chat message). + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/context-menu.html + */ +class ImContextMenuPlacementOptions extends AbstractPlacementOptions +{ +} diff --git a/src/Services/IM/Placements/ImNavigationPlacementOptions.php b/src/Services/IM/Placements/ImNavigationPlacementOptions.php new file mode 100644 index 00000000..f4a2a227 --- /dev/null +++ b/src/Services/IM/Placements/ImNavigationPlacementOptions.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Fluent options builder for the `IM_NAVIGATION` placement. + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ +class ImNavigationPlacementOptions extends ImTextareaPlacementOptions +{ +} diff --git a/src/Services/IM/Placements/ImSidebarPlacementOptions.php b/src/Services/IM/Placements/ImSidebarPlacementOptions.php new file mode 100644 index 00000000..940906ce --- /dev/null +++ b/src/Services/IM/Placements/ImSidebarPlacementOptions.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Fluent options builder for the `IM_SIDEBAR` placement (chat sidebar widget). + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/sidebar.html + */ +class ImSidebarPlacementOptions extends AbstractPlacementOptions +{ + public function __construct(string $iconName) + { + $this->fields['iconName'] = $iconName; + } + + public function color(PlacementColor $placementColor): self + { + $this->fields['color'] = $placementColor->value; + + return $this; + } +} diff --git a/src/Services/IM/Placements/ImTextareaPlacementOptions.php b/src/Services/IM/Placements/ImTextareaPlacementOptions.php new file mode 100644 index 00000000..e4dd055c --- /dev/null +++ b/src/Services/IM/Placements/ImTextareaPlacementOptions.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Fluent options builder for the `IM_TEXTAREA` placement + * (widget panel above the chat message input field). + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/textarea.html + */ +class ImTextareaPlacementOptions extends AbstractPlacementOptions +{ + public function __construct(string $iconName) + { + $this->fields['iconName'] = $iconName; + } + + public function color(PlacementColor $placementColor): self + { + $this->fields['color'] = $placementColor->value; + + return $this; + } + + public function width(int $width): self + { + $this->fields['width'] = $width; + + return $this; + } + + public function height(int $height): self + { + $this->fields['height'] = $height; + + return $this; + } +} diff --git a/src/Services/IM/Placements/PlacementColor.php b/src/Services/IM/Placements/PlacementColor.php new file mode 100644 index 00000000..47f7ed74 --- /dev/null +++ b/src/Services/IM/Placements/PlacementColor.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * Color palette accepted by IM widget placement options (`IM_TEXTAREA`, `IM_SIDEBAR`). + */ +enum PlacementColor: string +{ + case Red = 'RED'; + case Green = 'GREEN'; + case Mint = 'MINT'; + case LightBlue = 'LIGHT_BLUE'; + case DarkBlue = 'DARK_BLUE'; + case Purple = 'PURPLE'; + case Aqua = 'AQUA'; + case Pink = 'PINK'; + case Lime = 'LIME'; + case Brown = 'BROWN'; + case Azure = 'AZURE'; + case Khaki = 'KHAKI'; + case Sand = 'SAND'; + case Orange = 'ORANGE'; + case Marengo = 'MARENGO'; + case Gray = 'GRAY'; + case Graphite = 'GRAPHITE'; +} diff --git a/src/Services/IM/Placements/PlacementLangItem.php b/src/Services/IM/Placements/PlacementLangItem.php new file mode 100644 index 00000000..b45f4476 --- /dev/null +++ b/src/Services/IM/Placements/PlacementLangItem.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +final readonly class PlacementLangItem +{ + public function __construct( + public string $title, + public ?string $description = null, + public ?string $groupName = null, + ) { + } + + /** + * @return array{TITLE: string, DESCRIPTION?: string, GROUP_NAME?: string} + */ + public function toArray(): array + { + $result = [ + 'TITLE' => $this->title, + ]; + + if ($this->description !== null) { + $result['DESCRIPTION'] = $this->description; + } + + if ($this->groupName !== null) { + $result['GROUP_NAME'] = $this->groupName; + } + + return $result; + } +} diff --git a/src/Services/IM/Placements/PlacementLangMap.php b/src/Services/IM/Placements/PlacementLangMap.php new file mode 100644 index 00000000..3fd110e1 --- /dev/null +++ b/src/Services/IM/Placements/PlacementLangMap.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +use Bitrix24\SDK\Core\Contracts\LangCodes; + +final readonly class PlacementLangMap +{ + /** + * @param array, PlacementLangItem> $items + */ + private function __construct(private array $items) + { + } + + public static function empty(): self + { + return new self([]); + } + + public function with(LangCodes $langCodes, PlacementLangItem $placementLangItem): self + { + $items = $this->items; + $items[$langCodes->value] = $placementLangItem; + + return new self($items); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = []; + + foreach ($this->items as $langCode => $placementLangItem) { + $result[$langCode] = $placementLangItem->toArray(); + } + + return $result; + } +} diff --git a/src/Services/IM/Placements/PlacementLocationCodes.php b/src/Services/IM/Placements/PlacementLocationCodes.php new file mode 100644 index 00000000..e2c4dd0f --- /dev/null +++ b/src/Services/IM/Placements/PlacementLocationCodes.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +/** + * IM widget placement codes (scope `im`). + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ +class PlacementLocationCodes +{ + // Widget panel above the chat message input field. + // See https://apidocs.bitrix24.com/api-reference/widgets/im/textarea.html + public const string IM_TEXTAREA = 'IM_TEXTAREA'; + + // Chat sidebar widget. + // See https://apidocs.bitrix24.com/api-reference/widgets/im/sidebar.html + public const string IM_SIDEBAR = 'IM_SIDEBAR'; + + // Context-menu item on a chat message ("Create content based on"). + // See https://apidocs.bitrix24.com/api-reference/widgets/im/context-menu.html + public const string IM_CONTEXT_MENU = 'IM_CONTEXT_MENU'; + + // Main navigation panel item in messenger. + // See https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + public const string IM_NAVIGATION = 'IM_NAVIGATION'; + + /** + * Smiles / Giphy selector pop-up. + * + * @deprecated No longer works since module `im 25.1600.0` — smiles were + * replaced by stickers. See + * https://apidocs.bitrix24.com/api-reference/widgets/im/smile-selector.html + */ + public const string IM_SMILES_SELECTOR = 'IM_SMILES_SELECTOR'; +} diff --git a/src/Services/IM/Placements/Placements.php b/src/Services/IM/Placements/Placements.php new file mode 100644 index 00000000..c08ea62b --- /dev/null +++ b/src/Services/IM/Placements/Placements.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Placements; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Placement\Result\PlacementBindResult; +use Bitrix24\SDK\Services\Placement\Result\PlacementUnbindResult; +use Bitrix24\SDK\Services\Placement\Service\Placement; + +/** + * Typed placement registration helpers for IM widgets. + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ +final readonly class Placements +{ + public function __construct(private Placement $placementService) + { + } + + /** + * Register the `IM_SIDEBAR` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/sidebar.html + */ + public function bindSidebar( + string $handlerUrl, + PlacementLangMap $placementLangMap, + ImSidebarPlacementOptions $imSidebarPlacementOptions, + ?int $b24UserId = null, + ): PlacementBindResult { + return $this->placementService->bind( + PlacementLocationCodes::IM_SIDEBAR, + $handlerUrl, + $placementLangMap->toArray(), + $imSidebarPlacementOptions, + $b24UserId, + ); + } + + /** + * Register the `IM_NAVIGATION` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ + public function bindNavigation( + string $handlerUrl, + PlacementLangMap $placementLangMap, + ImNavigationPlacementOptions $imNavigationPlacementOptions, + ?int $b24UserId = null, + ): PlacementBindResult { + return $this->placementService->bind( + PlacementLocationCodes::IM_NAVIGATION, + $handlerUrl, + $placementLangMap->toArray(), + $imNavigationPlacementOptions, + $b24UserId, + ); + } + + /** + * Register the `IM_CONTEXT_MENU` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/context-menu.html + */ + public function bindContextMenu( + string $handlerUrl, + PlacementLangMap $placementLangMap, + ImContextMenuPlacementOptions $imContextMenuPlacementOptions, + ?int $b24UserId = null, + ): PlacementBindResult { + return $this->placementService->bind( + PlacementLocationCodes::IM_CONTEXT_MENU, + $handlerUrl, + $placementLangMap->toArray(), + $imContextMenuPlacementOptions, + $b24UserId, + ); + } + + /** + * Register the `IM_TEXTAREA` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/textarea.html + */ + public function bindTextarea( + string $handlerUrl, + PlacementLangMap $placementLangMap, + ImTextareaPlacementOptions $imTextareaPlacementOptions, + ?int $b24UserId = null, + ): PlacementBindResult { + return $this->placementService->bind( + PlacementLocationCodes::IM_TEXTAREA, + $handlerUrl, + $placementLangMap->toArray(), + $imTextareaPlacementOptions, + $b24UserId, + ); + } + + /** + * Register the deprecated `IM_SMILES_SELECTOR` placement handler. + * + * @throws BaseException + * @throws TransportException + * @deprecated + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/smile-selector.html + */ + public function bindSmilesSelector( + string $handlerUrl, + PlacementLangMap $placementLangMap, + array $imSmilesSelectorPlacementOptions = [], + ?int $b24UserId = null, + ): PlacementBindResult { + return $this->placementService->bind( + PlacementLocationCodes::IM_SMILES_SELECTOR, + $handlerUrl, + $placementLangMap->toArray(), + $imSmilesSelectorPlacementOptions, + $b24UserId, + ); + } + + /** + * Unregister the `IM_SIDEBAR` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/sidebar.html + */ + public function unbindSidebar(?string $handlerUrl = null): PlacementUnbindResult + { + return $this->placementService->unbind(PlacementLocationCodes::IM_SIDEBAR, $handlerUrl); + } + + /** + * Unregister the `IM_NAVIGATION` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/index.html + */ + public function unbindNavigation(?string $handlerUrl = null): PlacementUnbindResult + { + return $this->placementService->unbind(PlacementLocationCodes::IM_NAVIGATION, $handlerUrl); + } + + /** + * Unregister the `IM_CONTEXT_MENU` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/context-menu.html + */ + public function unbindContextMenu(?string $handlerUrl = null): PlacementUnbindResult + { + return $this->placementService->unbind(PlacementLocationCodes::IM_CONTEXT_MENU, $handlerUrl); + } + + /** + * Unregister the `IM_TEXTAREA` placement handler. + * + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/textarea.html + */ + public function unbindTextarea(?string $handlerUrl = null): PlacementUnbindResult + { + return $this->placementService->unbind(PlacementLocationCodes::IM_TEXTAREA, $handlerUrl); + } + + /** + * Unregister the deprecated `IM_SMILES_SELECTOR` placement handler. + * + * @deprecated + * @throws BaseException + * @throws TransportException + * @link https://apidocs.bitrix24.com/api-reference/widgets/im/smile-selector.html + */ + public function unbindSmilesSelector(?string $handlerUrl = null): PlacementUnbindResult + { + return $this->placementService->unbind(PlacementLocationCodes::IM_SMILES_SELECTOR, $handlerUrl); + } +} diff --git a/src/Services/IM/Recent/Result/RecentItemResult.php b/src/Services/IM/Recent/Result/RecentItemResult.php new file mode 100644 index 00000000..a50bdf6c --- /dev/null +++ b/src/Services/IM/Recent/Result/RecentItemResult.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Recent\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read string $id + * @property-read string $type + * @property-read array $avatar + * @property-read string $title + * @property-read int $counter + * @property-read bool $unread + * @property-read bool $pinned + * @property-read int $chat_id + * @property-read array $message + * @property-read int $last_id + * @property-read bool $has_reminder + * @property-read CarbonImmutable $date_update + * @property-read CarbonImmutable $date_last_activity + * @property-read array $chat + * @property-read array $user + * @property-read array $options + */ +class RecentItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Recent/Result/RecentsResult.php b/src/Services/IM/Recent/Result/RecentsResult.php new file mode 100644 index 00000000..c0fb555e --- /dev/null +++ b/src/Services/IM/Recent/Result/RecentsResult.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Recent\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RecentsResult extends AbstractResult +{ + /** + * @return RecentItemResult[] + * @throws BaseException + */ + public function items(): array + { + return array_values(array_map( + static fn (array $item): RecentItemResult => new RecentItemResult($item), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } +} diff --git a/src/Services/IM/Recent/Service/Recent.php b/src/Services/IM/Recent/Service/Recent.php new file mode 100644 index 00000000..5dd9e29b --- /dev/null +++ b/src/Services/IM/Recent/Service/Recent.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Recent\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Recent\Result\RecentsResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Recent extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.recent.get', + 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-get.html', + 'Get a shortened list of recent chats' + )] + public function get(?int $lastId = null, ?int $limit = null): RecentsResult + { + $payload = [ + 'LAST_ID' => $lastId, + 'LIMIT' => $limit, + ]; + + return new RecentsResult($this->core->call('im.recent.get', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.recent.list', + 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-list.html', + 'Get the list of recent dialogs with pagination' + )] + public function list(?int $lastId = null, ?int $limit = null): RecentsResult + { + $payload = [ + 'LAST_ID' => $lastId, + 'LIMIT' => $limit, + ]; + + return new RecentsResult($this->core->call('im.recent.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.recent.pin', + 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-pin.html', + 'Pin or unpin a dialog at the top of the recent list' + )] + public function pin(string $dialogId, bool $pin = true): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.recent.pin', [ + 'DIALOG_ID' => $dialogId, + 'PIN' => $pin ? 'Y' : 'N', + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.recent.unread', + 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-unread.html', + 'Set or remove the unread mark on a dialog' + )] + public function unread(string $dialogId, string $action): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.recent.unread', [ + 'DIALOG_ID' => $dialogId, + 'ACTION' => $action, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.recent.hide', + 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-hide.html', + 'Remove a dialog from the recent list' + )] + public function hide(string $dialogId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.recent.hide', [ + 'DIALOG_ID' => $dialogId, + ])); + } +} diff --git a/src/Services/IM/Revision/Result/RevisionItemResult.php b/src/Services/IM/Revision/Result/RevisionItemResult.php new file mode 100644 index 00000000..b40468c4 --- /dev/null +++ b/src/Services/IM/Revision/Result/RevisionItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +// This class was automatically generated by the b24-dev:result-item-generator command. +// Source: payload +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Revision\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $rest + * @property-read int $web + * @property-read int $mobile + * @property-read int $desktop + * @property-read int $im_revision_mobile + */ +class RevisionItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Revision/Result/RevisionResult.php b/src/Services/IM/Revision/Result/RevisionResult.php new file mode 100644 index 00000000..c5d02462 --- /dev/null +++ b/src/Services/IM/Revision/Result/RevisionResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Revision\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class RevisionResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function revision(): RevisionItemResult + { + return new RevisionItemResult($this->getCoreResponse()->getResponseData()->getResult()); + } +} diff --git a/src/Services/IM/Revision/Service/Revision.php b/src/Services/IM/Revision/Service/Revision.php new file mode 100644 index 00000000..53cf1f02 --- /dev/null +++ b/src/Services/IM/Revision/Service/Revision.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Revision\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Revision\Result\RevisionResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Revision extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.revision.get', + 'https://apidocs.bitrix24.com/api-reference/chats/im-revision-get.html', + 'Get IM module API revision numbers for client/server compatibility checks' + )] + public function get(): RevisionResult + { + return new RevisionResult($this->core->call('im.revision.get')); + } +} diff --git a/src/Services/IM/Search/Result/SearchChatItemResult.php b/src/Services/IM/Search/Result/SearchChatItemResult.php new file mode 100644 index 00000000..80614ada --- /dev/null +++ b/src/Services/IM/Search/Result/SearchChatItemResult.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read int $parent_chat_id + * @property-read int $parent_message_id + * @property-read string $name + * @property-read string|null $description + * @property-read int $owner + * @property-read bool $extranet + * @property-read string $avatar + * @property-read string $color + * @property-read string $type + * @property-read int $counter + * @property-read int $user_counter + * @property-read int $message_count + * @property-read int $unread_id + * @property-read array $restrictions + * @property-read int $last_message_id + * @property-read int $last_id + * @property-read int $marked_id + * @property-read int $disk_folder_id + * @property-read string $entity_type + * @property-read string $entity_id + * @property-read string $entity_data_1 + * @property-read string $entity_data_2 + * @property-read string $entity_data_3 + * @property-read array $mute_list + * @property-read CarbonImmutable $date_create + * @property-read string $message_type + * @property-read string $public + * @property-read string $role + * @property-read array $entity_link + * @property-read bool $text_field_enabled + * @property-read int|null $background_id + * @property-read array $permissions + * @property-read bool $is_new + */ +class SearchChatItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Search/Result/SearchChatsResult.php b/src/Services/IM/Search/Result/SearchChatsResult.php new file mode 100644 index 00000000..c4754aac --- /dev/null +++ b/src/Services/IM/Search/Result/SearchChatsResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SearchChatsResult extends AbstractResult +{ + /** + * @return SearchChatItemResult[] + * @throws BaseException + */ + public function items(): array + { + return array_values(array_map( + static fn (array $item): SearchChatItemResult => new SearchChatItemResult($item), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } + + /** + * @throws BaseException + */ + public function total(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } + + /** + * @throws BaseException + */ + public function next(): ?int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getNextItem(); + } +} diff --git a/src/Services/IM/Search/Result/SearchDepartmentItemResult.php b/src/Services/IM/Search/Result/SearchDepartmentItemResult.php new file mode 100644 index 00000000..db355dac --- /dev/null +++ b/src/Services/IM/Search/Result/SearchDepartmentItemResult.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read string $name + * @property-read string $full_name + * @property-read int $manager_user_id + * @property-read array|null $manager_user_data + */ +class SearchDepartmentItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Search/Result/SearchDepartmentsResult.php b/src/Services/IM/Search/Result/SearchDepartmentsResult.php new file mode 100644 index 00000000..d7515bc1 --- /dev/null +++ b/src/Services/IM/Search/Result/SearchDepartmentsResult.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SearchDepartmentsResult extends AbstractResult +{ + /** + * @return SearchDepartmentItemResult[] + * @throws BaseException + */ + public function items(): array + { + return array_values(array_map( + static fn (array $item): SearchDepartmentItemResult => new SearchDepartmentItemResult($item), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } + + /** + * @throws BaseException + */ + public function total(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } + + /** + * @throws BaseException + */ + public function next(): ?int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getNextItem(); + } +} diff --git a/src/Services/IM/Search/Result/SearchLastItemsResult.php b/src/Services/IM/Search/Result/SearchLastItemsResult.php new file mode 100644 index 00000000..1d7c811d --- /dev/null +++ b/src/Services/IM/Search/Result/SearchLastItemsResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SearchLastItemsResult extends AbstractResult +{ + /** + * @return array> + * @throws BaseException + */ + public function items(): array + { + return array_values(array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array')); + } +} diff --git a/src/Services/IM/Search/Result/SearchUserItemResult.php b/src/Services/IM/Search/Result/SearchUserItemResult.php new file mode 100644 index 00000000..c5a0a44b --- /dev/null +++ b/src/Services/IM/Search/Result/SearchUserItemResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * @property-read int $id + * @property-read string $name + * @property-read string $first_name + * @property-read string|null $last_name + * @property-read string|null $work_position + * @property-read string $color + * @property-read string $avatar + * @property-read string $gender + * @property-read string $birthday + * @property-read bool $extranet + * @property-read bool $network + * @property-read bool $bot + * @property-read bool $connector + * @property-read string $external_auth_id + * @property-read string|null $status + * @property-read bool $idle + * @property-read string $last_activity_date + * @property-read string $mobile_last_date + * @property-read array $departments + * @property-read bool $absent + * @property-read array|null $phones + */ +class SearchUserItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/Search/Result/SearchUsersResult.php b/src/Services/IM/Search/Result/SearchUsersResult.php new file mode 100644 index 00000000..3b1c93bf --- /dev/null +++ b/src/Services/IM/Search/Result/SearchUsersResult.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class SearchUsersResult extends AbstractResult +{ + /** + * @return SearchUserItemResult[] + * @throws BaseException + */ + public function items(): array + { + return array_values(array_map( + static fn (array $item): SearchUserItemResult => new SearchUserItemResult($item), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } + + /** + * @throws BaseException + */ + public function total(): int + { + return $this->getCoreResponse()->getResponseData()->getPagination()->getTotal() ?? 0; + } +} diff --git a/src/Services/IM/Search/Service/Search.php b/src/Services/IM/Search/Service/Search.php new file mode 100644 index 00000000..1b376c50 --- /dev/null +++ b/src/Services/IM/Search/Service/Search.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\Search\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\Search\Result\SearchChatsResult; +use Bitrix24\SDK\Services\IM\Search\Result\SearchDepartmentsResult; +use Bitrix24\SDK\Services\IM\Search\Result\SearchLastItemsResult; +use Bitrix24\SDK\Services\IM\Search\Result\SearchUsersResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class Search extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.search.chat.list', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-chat-list.html', + 'Search chats available to the current user' + )] + public function chatList( + ?string $find = null, + ?string $findLines = null, + ?int $offset = null, + ?int $limit = null, + ): SearchChatsResult { + $payload = [ + 'FIND' => $find, + 'FIND_LINES' => $findLines, + 'OFFSET' => $offset, + 'LIMIT' => $limit, + ]; + + return new SearchChatsResult($this->core->call('im.search.chat.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.search.user.list', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-user-list.html', + 'Search users by name' + )] + public function userList(string $find, ?int $offset = null, ?int $limit = null): SearchUsersResult + { + $payload = [ + 'FIND' => $find, + 'OFFSET' => $offset, + 'LIMIT' => $limit, + ]; + + return new SearchUsersResult($this->core->call('im.search.user.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.search.department.list', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-department-list.html', + 'Search departments by full name' + )] + public function departmentList( + string $find, + bool $userData = false, + ?int $offset = null, + ?int $limit = null, + ): SearchDepartmentsResult { + $payload = [ + 'FIND' => $find, + 'USER_DATA' => $userData ? 'Y' : 'N', + 'OFFSET' => $offset, + 'LIMIT' => $limit, + ]; + + return new SearchDepartmentsResult($this->core->call('im.search.department.list', array_filter( + $payload, + static fn (mixed $value): bool => $value !== null + ))); + } + + /** + * @throws BaseException + * @throws TransportException + * @deprecated Developed for the previous chat UI; results are not shown in the current M1 chat interface. + */ + #[ApiEndpointMetadata( + 'im.search.last.add', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-last-add.html', + 'Add a dialog to the legacy last search history', + isDeprecated: true, + deprecationMessage: 'Developed for the previous chat UI; results are not shown in the current M1 chat interface.' + )] + public function lastAdd(string $dialogId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.search.last.add', [ + 'DIALOG_ID' => $dialogId, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + * @deprecated Developed for the previous chat UI; results are not shown in the current M1 chat interface. + */ + #[ApiEndpointMetadata( + 'im.search.last.get', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-last-get.html', + 'Get legacy last search history', + isDeprecated: true, + deprecationMessage: 'Developed for the previous chat UI; results are not shown in the current M1 chat interface.' + )] + public function lastGet( + bool $skipOpenLines = false, + bool $skipChat = false, + bool $skipDialog = false, + ): SearchLastItemsResult { + return new SearchLastItemsResult($this->core->call('im.search.last.get', [ + 'SKIP_OPENLINES' => $skipOpenLines ? 'Y' : 'N', + 'SKIP_CHAT' => $skipChat ? 'Y' : 'N', + 'SKIP_DIALOG' => $skipDialog ? 'Y' : 'N', + ])); + } + + /** + * @throws BaseException + * @throws TransportException + * @deprecated Developed for the previous chat UI; results are not shown in the current M1 chat interface. + */ + #[ApiEndpointMetadata( + 'im.search.last.delete', + 'https://apidocs.bitrix24.com/api-reference/chats/search/im-search-last-delete.html', + 'Delete a dialog from the legacy last search history', + isDeprecated: true, + deprecationMessage: 'Developed for the previous chat UI; results are not shown in the current M1 chat interface.' + )] + public function lastDelete(string $dialogId): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.search.last.delete', [ + 'DIALOG_ID' => $dialogId, + ])); + } +} diff --git a/src/Services/IM/User/Result/UserItemResult.php b/src/Services/IM/User/Result/UserItemResult.php new file mode 100644 index 00000000..36eecf40 --- /dev/null +++ b/src/Services/IM/User/Result/UserItemResult.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * @property-read int $id + * @property-read bool $active + * @property-read string $name + * @property-read string $first_name + * @property-read string $last_name + * @property-read string|null $work_position + * @property-read string $color + * @property-read string $avatar + * @property-read string $avatar_hr + * @property-read string $gender + * @property-read string $birthday + * @property-read bool $extranet + * @property-read bool $network + * @property-read bool $bot + * @property-read bool $connector + * @property-read string $external_auth_id + * @property-read string $status + * @property-read bool $idle + * @property-read CarbonImmutable $last_activity_date + * @property-read bool $mobile_last_date + * @property-read bool $desktop_last_date + * @property-read bool $absent + * @property-read array $departments + * @property-read array $phones + * @property-read array|null $bot_data + * @property-read string $type + * @property-read string $website + * @property-read string $email + */ +class UserItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/User/Result/UserResult.php b/src/Services/IM/User/Result/UserResult.php new file mode 100644 index 00000000..973bc851 --- /dev/null +++ b/src/Services/IM/User/Result/UserResult.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class UserResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function user(): ?UserItemResult + { + $result = $this->getCoreResponse()->getResponseData()->getResult(); + if (!array_is_list($result)) { + return new UserItemResult($result); + } + + if (!is_array($result[0] ?? null)) { + return null; + } + + return new UserItemResult($result[0]); + } +} diff --git a/src/Services/IM/User/Result/UserStatusItemResult.php b/src/Services/IM/User/Result/UserStatusItemResult.php new file mode 100644 index 00000000..ac26f7bc --- /dev/null +++ b/src/Services/IM/User/Result/UserStatusItemResult.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Bitrix24\SDK\Services\IM\User\UserStatusType; + +/** + * @property-read UserStatusType|null $STATUS + */ +class UserStatusItemResult extends AbstractItem +{ +} diff --git a/src/Services/IM/User/Result/UserStatusResult.php b/src/Services/IM/User/Result/UserStatusResult.php new file mode 100644 index 00000000..51ca5b1a --- /dev/null +++ b/src/Services/IM/User/Result/UserStatusResult.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class UserStatusResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function status(): UserStatusItemResult + { + return new UserStatusItemResult([ + 'STATUS' => $this->getCoreResponse()->getResponseData()->getResult()[0] ?? null, + ]); + } +} diff --git a/src/Services/IM/User/Result/UsersResult.php b/src/Services/IM/User/Result/UsersResult.php new file mode 100644 index 00000000..2fe1a485 --- /dev/null +++ b/src/Services/IM/User/Result/UsersResult.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +class UsersResult extends AbstractResult +{ + /** + * @return UserItemResult[] + * @throws BaseException + */ + public function users(): array + { + return array_values(array_map( + static fn (array $user): UserItemResult => new UserItemResult($user), + array_filter($this->getCoreResponse()->getResponseData()->getResult(), 'is_array') + )); + } +} diff --git a/src/Services/IM/User/Service/User.php b/src/Services/IM/User/Service/User.php new file mode 100644 index 00000000..db685674 --- /dev/null +++ b/src/Services/IM/User/Service/User.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\User\Result\UserResult; +use Bitrix24\SDK\Services\IM\User\Result\UsersResult; + +#[ApiServiceMetadata(new Scope(['im']))] +class User extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.get', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-get.html', + 'Get data for the current user or by user ID' + )] + public function get(?int $userId = null): UserResult + { + $payload = []; + if ($userId !== null) { + $payload['ID'] = $userId; + } + + return new UserResult($this->core->call('im.user.get', $payload)); + } + + /** + * @param int[] $userIds + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.list.get', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-list-get.html', + 'Get data for a list of users by their IDs' + )] + public function listGet(array $userIds): UsersResult + { + return new UsersResult($this->core->call('im.user.list.get', [ + 'ID' => $userIds, + ])); + } +} diff --git a/src/Services/IM/User/Service/UserStatus.php b/src/Services/IM/User/Service/UserStatus.php new file mode 100644 index 00000000..a9f82ae9 --- /dev/null +++ b/src/Services/IM/User/Service/UserStatus.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IM\User\Result\UserStatusResult; +use Bitrix24\SDK\Services\IM\User\UserStatusType; + +#[ApiServiceMetadata(new Scope(['im']))] +class UserStatus extends AbstractService +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.status.get', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-status-get.html', + 'Get current user status' + )] + public function get(): UserStatusResult + { + return new UserStatusResult($this->core->call('im.user.status.get')); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.status.set', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-status-set.html', + 'Set current user status' + )] + public function set(UserStatusType $userStatusType): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.user.status.set', [ + 'STATUS' => $userStatusType->value, + ])); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.status.idle.start', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-status-idle-start.html', + 'Enable automatic Away status' + )] + public function idleStart(): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.user.status.idle.start')); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'im.user.status.idle.end', + 'https://apidocs.bitrix24.com/api-reference/chats/users/im-user-status-idle-end.html', + 'Disable automatic Away status' + )] + public function idleEnd(): UpdatedItemResult + { + return new UpdatedItemResult($this->core->call('im.user.status.idle.end')); + } +} diff --git a/src/Services/IM/User/UserStatusType.php b/src/Services/IM/User/UserStatusType.php new file mode 100644 index 00000000..69f344fb --- /dev/null +++ b/src/Services/IM/User/UserStatusType.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IM\User; + +enum UserStatusType: string +{ + case Online = 'online'; + case Idle = 'idle'; + case Away = 'away'; + case Dnd = 'dnd'; + case Break = 'break'; + case Offline = 'offline'; +} diff --git a/src/Services/IMBot/Bot/BotBackground.php b/src/Services/IMBot/Bot/BotBackground.php new file mode 100644 index 00000000..ba4ec57f --- /dev/null +++ b/src/Services/IMBot/Bot/BotBackground.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot; + +/** + * Chat background for a chat-bot. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-register.html + */ +enum BotBackground: string +{ + case azure = 'azure'; + case mint = 'mint'; + case steel = 'steel'; + case slate = 'slate'; + case teal = 'teal'; + case cornflower = 'cornflower'; + case sky = 'sky'; + case peach = 'peach'; + case frost = 'frost'; +} diff --git a/src/Services/IMBot/Bot/BotEventMode.php b/src/Services/IMBot/Bot/BotEventMode.php new file mode 100644 index 00000000..0e89c005 --- /dev/null +++ b/src/Services/IMBot/Bot/BotEventMode.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot; + +/** + * Event delivery mode for a chat-bot. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-register.html + */ +enum BotEventMode: string +{ + /** Bot polls for events via imbot.v2.Event.get */ + case fetch = 'fetch'; + + /** Events are pushed via POST to the bot's webhookUrl */ + case webhook = 'webhook'; +} diff --git a/src/Services/IMBot/Bot/BotType.php b/src/Services/IMBot/Bot/BotType.php new file mode 100644 index 00000000..9c663c6e --- /dev/null +++ b/src/Services/IMBot/Bot/BotType.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot; + +/** + * Chat-bot type. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-register.html + */ +enum BotType: string +{ + case bot = 'bot'; + case network = 'network'; + case openline = 'openline'; + case supervisor = 'supervisor'; + case personal = 'personal'; +} diff --git a/src/Services/IMBot/Bot/Result/BotItemResult.php b/src/Services/IMBot/Bot/Result/BotItemResult.php new file mode 100644 index 00000000..4fddbeaf --- /dev/null +++ b/src/Services/IMBot/Bot/Result/BotItemResult.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * Single chat-bot item returned by imbot.v2.Bot.* methods. + * + * @property-read int $id + * @property-read string $code + * @property-read string $type + * @property-read bool $isHidden + * @property-read bool $isSupportOpenline + * @property-read bool $isReactionsEnabled + * @property-read ?string $backgroundId + * @property-read string $language + * @property-read string $moduleId + * @property-read string $eventMode + * @property-read int $countMessage + * @property-read int $countCommand + * @property-read int $countChat + * @property-read int $countUser + */ +class BotItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/Bot/Result/BotResult.php b/src/Services/IMBot/Bot/Result/BotResult.php new file mode 100644 index 00000000..5e8bf7c9 --- /dev/null +++ b/src/Services/IMBot/Bot/Result/BotResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Bot.register / imbot.v2.Bot.get / imbot.v2.Bot.update. + * + * Response shape: { result: { bot: {...}, users: [...] } } + */ +class BotResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function bot(): BotItemResult + { + return new BotItemResult($this->getCoreResponse()->getResponseData()->getResult()['bot']); + } + + /** + * @return array + * + * @throws BaseException + */ + public function users(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['users'] ?? []; + } +} diff --git a/src/Services/IMBot/Bot/Result/BotsResult.php b/src/Services/IMBot/Bot/Result/BotsResult.php new file mode 100644 index 00000000..99675f4e --- /dev/null +++ b/src/Services/IMBot/Bot/Result/BotsResult.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Bot.list. + * + * Response shape: { result: { bots: [...], users: [...], hasNextPage: bool } } + */ +class BotsResult extends AbstractResult +{ + /** + * @return BotItemResult[] + * + * @throws BaseException + */ + public function bots(): array + { + return array_map( + static fn (array $bot): BotItemResult => new BotItemResult($bot), + $this->getCoreResponse()->getResponseData()->getResult()['bots'] ?? [] + ); + } + + /** + * @return array + * + * @throws BaseException + */ + public function users(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['users'] ?? []; + } + + /** + * @throws BaseException + */ + public function hasNextPage(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()['hasNextPage'] ?? false); + } +} diff --git a/src/Services/IMBot/Bot/Service/Bot.php b/src/Services/IMBot/Bot/Service/Bot.php new file mode 100644 index 00000000..35b12df0 --- /dev/null +++ b/src/Services/IMBot/Bot/Service/Bot.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Bot\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\EmptyResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Bot\BotBackground; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Result\BotResult; +use Bitrix24\SDK\Services\IMBot\Bot\Result\BotsResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class Bot extends AbstractService +{ + /** + * Register a new chat-bot. + * + * Idempotent: repeated calls with the same code return the existing bot without updating its data. + * + * @param array $properties Bot profile properties (name, firstName, lastName, workPosition, color, gender, avatar). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-register.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Bot.register', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-register.html', + 'Register a new chat-bot' + )] + public function register( + string $code, + array $properties, + ?string $botToken = null, + BotType $type = BotType::bot, + BotEventMode $eventMode = BotEventMode::fetch, + ?string $webhookUrl = null, + bool $isHidden = false, + bool $isReactionsEnabled = true, + bool $isSupportOpenline = false, + ?BotBackground $backgroundId = null, + ): BotResult { + $fields = [ + 'code' => $code, + 'properties' => $properties, + 'type' => $type->value, + 'eventMode' => $eventMode->value, + 'isHidden' => $isHidden, + 'isReactionsEnabled' => $isReactionsEnabled, + 'isSupportOpenline' => $isSupportOpenline, + ]; + + if ($botToken !== null) { + $fields['botToken'] = $botToken; + } + + if ($webhookUrl !== null) { + $fields['webhookUrl'] = $webhookUrl; + } + + if ($backgroundId instanceof BotBackground) { + $fields['backgroundId'] = $backgroundId->value; + } + + return new BotResult($this->core->call('imbot.v2.Bot.register', ['fields' => $fields])); + } + + /** + * Update an existing chat-bot. + * + * @param array $fields Fields to update (code, properties, type, eventMode, webhookUrl, isHidden, isReactionsEnabled, isSupportOpenline, backgroundId). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Bot.update', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-update.html', + 'Update an existing chat-bot' + )] + public function update( + int $botId, + array $fields, + ?string $botToken = null, + ): BotResult { + $params = [ + 'botId' => $botId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new BotResult($this->core->call('imbot.v2.Bot.update', $params)); + } + + /** + * Get information about a chat-bot by its ID or code. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Bot.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-get.html', + 'Get information about a chat-bot' + )] + public function get( + ?int $botId = null, + ?string $code = null, + ?string $botToken = null, + ): BotResult { + $params = []; + + if ($botId !== null) { + $params['botId'] = $botId; + } + + if ($code !== null) { + $params['code'] = $code; + } + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new BotResult($this->core->call('imbot.v2.Bot.get', $params)); + } + + /** + * Get the list of chat-bots for the current application. + * + * @param array $filter Filter parameters (e.g. ['type' => 'bot']). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-list.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Bot.list', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-list.html', + 'Get the list of chat-bots for the current application' + )] + public function list( + array $filter = [], + int $limit = 50, + int $offset = 0, + ?string $botToken = null, + ): BotsResult { + $params = [ + 'limit' => $limit, + 'offset' => $offset, + ]; + + if ($filter !== []) { + $params['filter'] = $filter; + } + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new BotsResult($this->core->call('imbot.v2.Bot.list', $params)); + } + + /** + * Unregister (delete) a chat-bot. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-unregister.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Bot.unregister', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/bots/bot-unregister.html', + 'Unregister a chat-bot' + )] + public function unregister( + int $botId, + ?string $botToken = null, + ): EmptyResult { + $params = ['botId' => $botId]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new EmptyResult($this->core->call('imbot.v2.Bot.unregister', $params)); + } +} diff --git a/src/Services/IMBot/Chat/ChatColor.php b/src/Services/IMBot/Chat/ChatColor.php new file mode 100644 index 00000000..45aa53ae --- /dev/null +++ b/src/Services/IMBot/Chat/ChatColor.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat; + +/** + * Available chat colours for imbot.v2.Chat.add. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-add.html + */ +enum ChatColor: string +{ + case red = 'red'; + case green = 'green'; + case mint = 'mint'; + case lightBlue = 'lightBlue'; + case darkBlue = 'darkBlue'; + case purple = 'purple'; + case aqua = 'aqua'; + case pink = 'pink'; + case lime = 'lime'; + case brown = 'brown'; + case azure = 'azure'; + case khaki = 'khaki'; + case sand = 'sand'; + case marengo = 'marengo'; + case gray = 'gray'; + case graphite = 'graphite'; +} diff --git a/src/Services/IMBot/Chat/Result/ChatItemResult.php b/src/Services/IMBot/Chat/Result/ChatItemResult.php new file mode 100644 index 00000000..103dcb05 --- /dev/null +++ b/src/Services/IMBot/Chat/Result/ChatItemResult.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * Single chat item returned by imbot.v2.Chat.* methods. + * + * @property-read int $id + * @property-read string $dialogId + * @property-read string $name + * @property-read string $description + * @property-read string $type + * @property-read string $messageType + * @property-read int $owner + * @property-read string $color + * @property-read string $avatar + * @property-read bool $extranet + * @property-read bool $containsCollaber + * @property-read string $entityType + * @property-read string $entityId + * @property-read string $entityData1 + * @property-read string $entityData2 + * @property-read string $entityData3 + * @property-read ?int $diskFolderId + * @property-read string $role + * @property-read ?int $parentChatId + * @property-read ?int $parentMessageId + * @property-read bool $isNew + * @property-read string $textFieldEnabled + * @property-read ?string $backgroundId + * @property-read ?CarbonImmutable $dateCreate + * @property-read ?int $lastMessageId + * @property-read ?int $markedId + * @property-read int $messageCount + * @property-read string $public + * @property-read ?int $unreadId + * @property-read int $userCounter + */ +class ChatItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/Chat/Result/ChatLeaveResult.php b/src/Services/IMBot/Chat/Result/ChatLeaveResult.php new file mode 100644 index 00000000..90a4a96a --- /dev/null +++ b/src/Services/IMBot/Chat/Result/ChatLeaveResult.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Chat.leave. + * + * Response shape: { result: { result: true } } + */ +class ChatLeaveResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function isSuccess(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()['result'] ?? false); + } +} diff --git a/src/Services/IMBot/Chat/Result/ChatResult.php b/src/Services/IMBot/Chat/Result/ChatResult.php new file mode 100644 index 00000000..e15bc509 --- /dev/null +++ b/src/Services/IMBot/Chat/Result/ChatResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Chat.add / imbot.v2.Chat.get / imbot.v2.Chat.update. + * + * Response shape: { result: { chat: {...}, users: [...] } } + */ +class ChatResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function chat(): ChatItemResult + { + return new ChatItemResult($this->getCoreResponse()->getResponseData()->getResult()['chat']); + } + + /** + * @return array + * + * @throws BaseException + */ + public function users(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['users'] ?? []; + } +} diff --git a/src/Services/IMBot/Chat/Result/ChatUserListResult.php b/src/Services/IMBot/Chat/Result/ChatUserListResult.php new file mode 100644 index 00000000..2ecccd7e --- /dev/null +++ b/src/Services/IMBot/Chat/Result/ChatUserListResult.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Chat.User.list. + * + * Response shape: { result: { users: [...] } } + */ +class ChatUserListResult extends AbstractResult +{ + /** + * @return array + * + * @throws BaseException + */ + public function users(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['users'] ?? []; + } +} diff --git a/src/Services/IMBot/Chat/Service/Chat.php b/src/Services/IMBot/Chat/Service/Chat.php new file mode 100644 index 00000000..f1caa228 --- /dev/null +++ b/src/Services/IMBot/Chat/Service/Chat.php @@ -0,0 +1,201 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Chat\ChatColor; +use Bitrix24\SDK\Services\IMBot\Chat\Result\ChatLeaveResult; +use Bitrix24\SDK\Services\IMBot\Chat\Result\ChatResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class Chat extends AbstractService +{ + /** + * Create a new group chat on behalf of the bot. + * + * @param int[] $userIds Array of member user IDs. + * @param array $fields Additional fields (description, avatar, message, ownerId). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.add', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-add.html', + 'Create a new group chat on behalf of the bot' + )] + public function add( + int $botId, + array $userIds, + ?string $title = null, + ?ChatColor $color = null, + array $fields = [], + ?string $botToken = null, + ): ChatResult { + $chatFields = array_merge($fields, ['userIds' => $userIds]); + + if ($title !== null) { + $chatFields['title'] = $title; + } + + if ($color instanceof ChatColor) { + $chatFields['color'] = $color->value; + } + + $params = [ + 'botId' => $botId, + 'fields' => $chatFields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatResult($this->core->call('imbot.v2.Chat.add', $params)); + } + + /** + * Get chat information by chat ID. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-get.html', + 'Get chat information by chat ID' + )] + public function get( + int $botId, + int $chatId, + ?string $botToken = null, + ): ChatResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatResult($this->core->call('imbot.v2.Chat.get', $params)); + } + + /** + * Update chat properties. + * + * @param array $fields Fields to update (title, description, color, avatar, ownerId, textFieldEnabled, backgroundId). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.update', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-update.html', + 'Update chat properties' + )] + public function update( + int $botId, + int $chatId, + array $fields, + ?string $botToken = null, + ): ChatResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatResult($this->core->call('imbot.v2.Chat.update', $params)); + } + + /** + * Remove the bot from a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-leave.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.leave', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-leave.html', + 'Remove the bot from a chat' + )] + public function leave( + int $botId, + int $chatId, + ?string $botToken = null, + ): ChatLeaveResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatLeaveResult($this->core->call('imbot.v2.Chat.leave', $params)); + } + + /** + * Transfer chat ownership to another user. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-set-owner.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.setOwner', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-set-owner.html', + 'Transfer chat ownership to another user' + )] + public function setOwner( + int $botId, + int $chatId, + int $userId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'userId' => $userId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.setOwner', $params)); + } +} diff --git a/src/Services/IMBot/Chat/Service/ChatInputAction.php b/src/Services/IMBot/Chat/Service/ChatInputAction.php new file mode 100644 index 00000000..289f6452 --- /dev/null +++ b/src/Services/IMBot/Chat/Service/ChatInputAction.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatInputAction extends AbstractService +{ + /** + * Send a typing indicator in the chat on behalf of the bot. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/ui/chat-input-action-notify.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.InputAction.notify', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/ui/chat-input-action-notify.html', + 'Send a typing indicator in the chat on behalf of the bot' + )] + public function notify( + int $botId, + string $dialogId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'dialogId' => $dialogId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.InputAction.notify', $params)); + } +} diff --git a/src/Services/IMBot/Chat/Service/ChatManager.php b/src/Services/IMBot/Chat/Service/ChatManager.php new file mode 100644 index 00000000..0733e7fe --- /dev/null +++ b/src/Services/IMBot/Chat/Service/ChatManager.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatManager extends AbstractService +{ + /** + * Assign manager rights to a chat member. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-manager-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Manager.add', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-manager-add.html', + 'Assign manager rights to a chat member' + )] + public function add( + int $botId, + int $chatId, + int $userId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'userId' => $userId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Manager.add', $params)); + } + + /** + * Revoke manager rights from a chat member. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-manager-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Manager.delete', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-manager-delete.html', + 'Revoke manager rights from a chat member' + )] + public function delete( + int $botId, + int $chatId, + int $userId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'userId' => $userId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Manager.delete', $params)); + } +} diff --git a/src/Services/IMBot/Chat/Service/ChatTextField.php b/src/Services/IMBot/Chat/Service/ChatTextField.php new file mode 100644 index 00000000..75e87256 --- /dev/null +++ b/src/Services/IMBot/Chat/Service/ChatTextField.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatTextField extends AbstractService +{ + /** + * Enable or disable the text field in a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/ui/chat-text-field-enabled.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.TextField.enabled', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/ui/chat-text-field-enabled.html', + 'Enable or disable the text field in a chat' + )] + public function enabled( + int $botId, + int $chatId, + bool $enable, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'enabled' => $enable, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.TextField.enabled', $params)); + } +} diff --git a/src/Services/IMBot/Chat/Service/ChatUser.php b/src/Services/IMBot/Chat/Service/ChatUser.php new file mode 100644 index 00000000..79372125 --- /dev/null +++ b/src/Services/IMBot/Chat/Service/ChatUser.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Chat\Result\ChatUserListResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatUser extends AbstractService +{ + /** + * Add users to a chat. + * + * @param int[] $userIds Array of user IDs to add. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.User.add', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-add.html', + 'Add users to a chat' + )] + public function add( + int $botId, + int $chatId, + array $userIds, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'userIds' => $userIds, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.User.add', $params)); + } + + /** + * Remove a user from a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.User.delete', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-delete.html', + 'Remove a user from a chat' + )] + public function delete( + int $botId, + int $chatId, + int $userId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + 'userId' => $userId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.User.delete', $params)); + } + + /** + * Get the list of users in a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-list.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.User.list', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/chats/chat-user-list.html', + 'Get the list of users in a chat' + )] + public function list( + int $botId, + int $chatId, + ?string $botToken = null, + ): ChatUserListResult { + $params = [ + 'botId' => $botId, + 'chatId' => $chatId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatUserListResult($this->core->call('imbot.v2.Chat.User.list', $params)); + } +} diff --git a/src/Services/IMBot/ChatMessage/Result/ChatMessageItemResult.php b/src/Services/IMBot/ChatMessage/Result/ChatMessageItemResult.php new file mode 100644 index 00000000..105cc2e2 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Result/ChatMessageItemResult.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * Single message item returned by imbot.v2.Chat.Message.get / imbot.v2.Chat.Message.getContext. + * + * @property-read int $id + * @property-read int $chatId + * @property-read int $authorId + * @property-read ?CarbonImmutable $date + * @property-read string $text + * @property-read bool $isSystem + * @property-read string $uuid + * @property-read bool $viewedByOthers + */ +class ChatMessageItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/ChatMessage/Result/ChatMessageResult.php b/src/Services/IMBot/ChatMessage/Result/ChatMessageResult.php new file mode 100644 index 00000000..7d3423f4 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Result/ChatMessageResult.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Chat.Message.get. + * + * Response shape: { result: { message: {...}, user: {...} } } + */ +class ChatMessageResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function message(): ChatMessageItemResult + { + return new ChatMessageItemResult( + $this->getCoreResponse()->getResponseData()->getResult()['message'] + ); + } + + /** + * @return array + * + * @throws BaseException + */ + public function user(): array + { + return $this->getCoreResponse()->getResponseData()->getResult()['user'] ?? []; + } +} diff --git a/src/Services/IMBot/ChatMessage/Result/ChatMessageSentBatchResult.php b/src/Services/IMBot/ChatMessage/Result/ChatMessageSentBatchResult.php new file mode 100644 index 00000000..7f1f9f61 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Result/ChatMessageSentBatchResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Result; + +use Bitrix24\SDK\Core\Response\DTO\ResponseData; + +/** + * Single result item for a batch imbot.v2.Chat.Message.send call. + * + * Response shape per item: { id: int, uuidMap: {} } + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-send.html + */ +class ChatMessageSentBatchResult +{ + public function __construct(private readonly ResponseData $responseData) + { + } + + public function getResponseData(): ResponseData + { + return $this->responseData; + } + + /** + * Returns the ID of the sent message. + */ + public function getId(): int + { + return (int)$this->responseData->getResult()['id']; + } +} diff --git a/src/Services/IMBot/ChatMessage/Result/ChatMessageSentResult.php b/src/Services/IMBot/ChatMessage/Result/ChatMessageSentResult.php new file mode 100644 index 00000000..fafe27e4 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Result/ChatMessageSentResult.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Chat.Message.send. + * + * Response shape: { result: { id: int, uuidMap: {} } } + */ +class ChatMessageSentResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function getId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['id']; + } + + /** + * @return array + * + * @throws BaseException + */ + public function getUuidMap(): array + { + return (array)($this->getCoreResponse()->getResponseData()->getResult()['uuidMap'] ?? []); + } +} diff --git a/src/Services/IMBot/ChatMessage/Service/Batch.php b/src/Services/IMBot/ChatMessage/Service/Batch.php new file mode 100644 index 00000000..f3ca7fc2 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Service/Batch.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Service; + +use Bitrix24\SDK\Attributes\ApiBatchMethodMetadata; +use Bitrix24\SDK\Attributes\ApiBatchServiceMetadata; +use Bitrix24\SDK\Core\Contracts\BatchOperationsInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\DeletedItemBatchResult; +use Bitrix24\SDK\Core\Result\UpdatedItemBatchResult; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Result\ChatMessageSentBatchResult; +use Generator; +use Psr\Log\LoggerInterface; + +/** + * Batch service for imbot.v2.Chat.Message.* methods. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/ + */ +#[ApiBatchServiceMetadata(new Scope(['imbot']))] +class Batch +{ + public function __construct( + protected readonly BatchOperationsInterface $batch, + protected readonly LoggerInterface $log + ) { + } + + /** + * Batch send messages on behalf of a bot. + * + * Each element of $messages must contain: + * - botId: int — bot identifier + * - dialogId: string — dialog or user ID to send to + * - fields: array — message fields (message, attach, keyboard, …) + * + * @param array}> $messages + * + * @return Generator + * + * @throws BaseException + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-send.html + */ + #[ApiBatchMethodMetadata( + 'imbot.v2.Chat.Message.send', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-send.html', + 'Batch send messages on behalf of a bot' + )] + public function send(array $messages): Generator + { + foreach ($this->batch->addEntityItems('imbot.v2.Chat.Message.send', $messages) as $key => $item) { + yield $key => new ChatMessageSentBatchResult($item); + } + } + + /** + * Batch delete messages sent by a bot. + * + * Each element of $messages must contain: + * - botId: int — bot identifier + * - messageId: int — message ID to delete + * + * @param array $messages + * + * @return Generator + * + * @throws BaseException + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-delete.html + */ + #[ApiBatchMethodMetadata( + 'imbot.v2.Chat.Message.delete', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-delete.html', + 'Batch delete messages sent by a bot' + )] + public function delete(array $messages): Generator + { + foreach ($this->batch->addEntityItems('imbot.v2.Chat.Message.delete', $messages) as $key => $item) { + yield $key => new DeletedItemBatchResult($item); + } + } + + /** + * Batch update messages sent by a bot. + * + * Each element of $messages must contain: + * - botId: int — bot identifier + * - messageId: int — message ID to update + * - fields: array — fields to update (message, attach, keyboard, urlPreview, isEdited) + * + * @param array}> $messages + * + * @return Generator + * + * @throws BaseException + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-update.html + */ + #[ApiBatchMethodMetadata( + 'imbot.v2.Chat.Message.update', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-update.html', + 'Batch update messages sent by a bot' + )] + public function update(array $messages): Generator + { + foreach ($this->batch->addEntityItems('imbot.v2.Chat.Message.update', $messages) as $key => $item) { + yield $key => new UpdatedItemBatchResult($item); + } + } +} diff --git a/src/Services/IMBot/ChatMessage/Service/ChatMessage.php b/src/Services/IMBot/ChatMessage/Service/ChatMessage.php new file mode 100644 index 00000000..4e5b0f07 --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Service/ChatMessage.php @@ -0,0 +1,293 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Contracts\CoreInterface; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\EmptyResult; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Result\ChatMessageResult; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Result\ChatMessageSentResult; +use Psr\Log\LoggerInterface; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatMessage extends AbstractService +{ + public function __construct(public readonly Batch $batch, CoreInterface $core, LoggerInterface $logger) + { + parent::__construct($core, $logger); + } + + /** + * Send a message on behalf of the bot. + * + * @param array|null $attach Message attachments. + * @param array|null $keyboard Keyboard buttons. + * @param array|null $forwardIds Messages to forward {uuid: messageId}. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-send.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.send', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-send.html', + 'Send a message on behalf of the bot' + )] + public function send( + int $botId, + string $dialogId, + ?string $message = null, + ?array $attach = null, + ?array $keyboard = null, + bool $system = false, + bool $urlPreview = true, + ?int $replyId = null, + ?string $templateId = null, + ?array $forwardIds = null, + ?string $botToken = null, + ): ChatMessageSentResult { + $fields = [ + 'system' => $system, + 'urlPreview' => $urlPreview, + ]; + + if ($message !== null) { + $fields['message'] = $message; + } + + if ($attach !== null) { + $fields['attach'] = $attach; + } + + if ($keyboard !== null) { + $fields['keyboard'] = $keyboard; + } + + if ($replyId !== null) { + $fields['replyId'] = $replyId; + } + + if ($templateId !== null) { + $fields['templateId'] = $templateId; + } + + if ($forwardIds !== null) { + $fields['forwardIds'] = $forwardIds; + } + + $params = [ + 'botId' => $botId, + 'dialogId' => $dialogId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatMessageSentResult($this->core->call('imbot.v2.Chat.Message.send', $params)); + } + + /** + * Update a previously sent message. + * + * @param array|null $attach Updated attachments. + * @param array|null $keyboard Updated keyboard. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.update', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-update.html', + 'Update a previously sent message' + )] + public function update( + int $botId, + int $messageId, + ?string $message = null, + ?array $attach = null, + ?array $keyboard = null, + ?bool $urlPreview = null, + ?bool $isEdited = null, + ?string $botToken = null, + ): UpdatedItemResult { + $fields = []; + + if ($message !== null) { + $fields['message'] = $message; + } + + if ($attach !== null) { + $fields['attach'] = $attach; + } + + if ($keyboard !== null) { + $fields['keyboard'] = $keyboard; + } + + if ($urlPreview !== null) { + $fields['urlPreview'] = $urlPreview; + } + + if ($isEdited !== null) { + $fields['isEdited'] = $isEdited; + } + + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Message.update', $params)); + } + + /** + * Delete a message sent by the bot. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.delete', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-delete.html', + 'Delete a message sent by the bot' + )] + public function delete( + int $botId, + int $messageId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Message.delete', $params)); + } + + /** + * Mark a message as read. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-read.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.read', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-read.html', + 'Mark a message as read' + )] + public function read( + int $botId, + int $messageId, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Message.read', $params)); + } + + /** + * Get a message by its ID. + * + * Available only for bots of type supervisor and personal. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-get.html', + 'Get a message by its ID' + )] + public function get( + int $botId, + int $messageId, + ?string $botToken = null, + ): ChatMessageResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new ChatMessageResult($this->core->call('imbot.v2.Chat.Message.get', $params)); + } + + /** + * Get messages context around a given message. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-get-context.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.getContext', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-get-context.html', + 'Get messages context around a given message' + )] + public function getContext( + int $botId, + int $messageId, + int $limit = 20, + ?string $botToken = null, + ): EmptyResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + 'limit' => $limit, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new EmptyResult($this->core->call('imbot.v2.Chat.Message.getContext', $params)); + } +} diff --git a/src/Services/IMBot/ChatMessage/Service/ChatMessageReaction.php b/src/Services/IMBot/ChatMessage/Service/ChatMessageReaction.php new file mode 100644 index 00000000..956c2a3e --- /dev/null +++ b/src/Services/IMBot/ChatMessage/Service/ChatMessageReaction.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\ChatMessage\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\UpdatedItemResult; +use Bitrix24\SDK\Services\AbstractService; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class ChatMessageReaction extends AbstractService +{ + /** + * Add a reaction to a message. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-reaction-add.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.Reaction.add', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-reaction-add.html', + 'Add a reaction to a message' + )] + public function add( + int $botId, + int $messageId, + string $reaction, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + 'reaction' => $reaction, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Message.Reaction.add', $params)); + } + + /** + * Remove a reaction from a message. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-reaction-delete.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Chat.Message.Reaction.delete', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/chat-message-reaction-delete.html', + 'Remove a reaction from a message' + )] + public function delete( + int $botId, + int $messageId, + string $reaction, + ?string $botToken = null, + ): UpdatedItemResult { + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + 'reaction' => $reaction, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new UpdatedItemResult($this->core->call('imbot.v2.Chat.Message.Reaction.delete', $params)); + } +} diff --git a/src/Services/IMBot/Command/Result/CommandItemResult.php b/src/Services/IMBot/Command/Result/CommandItemResult.php new file mode 100644 index 00000000..a5013a80 --- /dev/null +++ b/src/Services/IMBot/Command/Result/CommandItemResult.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Command\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * Single command item returned by imbot.v2.Command.* methods. + * + * @property-read int $id + * @property-read int $botId + * @property-read string $command + * @property-read bool $common + * @property-read bool $hidden + * @property-read bool $extranetSupport + */ +class CommandItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/Command/Result/CommandResult.php b/src/Services/IMBot/Command/Result/CommandResult.php new file mode 100644 index 00000000..c4ecd201 --- /dev/null +++ b/src/Services/IMBot/Command/Result/CommandResult.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Command\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Command.register / imbot.v2.Command.update. + * + * Response shape: { result: { command: {...} } } + */ +class CommandResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function command(): CommandItemResult + { + return new CommandItemResult( + $this->getCoreResponse()->getResponseData()->getResult()['command'] + ); + } +} diff --git a/src/Services/IMBot/Command/Result/CommandsResult.php b/src/Services/IMBot/Command/Result/CommandsResult.php new file mode 100644 index 00000000..1236a155 --- /dev/null +++ b/src/Services/IMBot/Command/Result/CommandsResult.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Command\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Command.list. + * + * Response shape: { result: { commands: [...] } } + */ +class CommandsResult extends AbstractResult +{ + /** + * @return CommandItemResult[] + * + * @throws BaseException + */ + public function commands(): array + { + return array_map( + static fn (array $command): CommandItemResult => new CommandItemResult($command), + $this->getCoreResponse()->getResponseData()->getResult()['commands'] ?? [] + ); + } +} diff --git a/src/Services/IMBot/Command/Service/Command.php b/src/Services/IMBot/Command/Service/Command.php new file mode 100644 index 00000000..2f27bf85 --- /dev/null +++ b/src/Services/IMBot/Command/Service/Command.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Command\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Core\Result\EmptyResult; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Command\Result\CommandResult; +use Bitrix24\SDK\Services\IMBot\Command\Result\CommandsResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class Command extends AbstractService +{ + /** + * Register a slash command for a bot. + * + * Idempotent: repeated calls with the same command return the existing command. + * + * @param array $title Localised titles: { 'en' => 'Show help', 'ru' => 'Помощь' }. + * @param array $params Localised parameter hints. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-register.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Command.register', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-register.html', + 'Register a slash command for a bot' + )] + public function register( + int $botId, + string $command, + array $title = [], + array $params = [], + bool $common = false, + bool $hidden = false, + bool $extranetSupport = false, + ?string $botToken = null, + ): CommandResult { + $fields = [ + 'command' => $command, + 'common' => $common, + 'hidden' => $hidden, + 'extranetSupport' => $extranetSupport, + ]; + + if ($title !== []) { + $fields['title'] = $title; + } + + if ($params !== []) { + $fields['params'] = $params; + } + + $apiParams = [ + 'botId' => $botId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $apiParams['botToken'] = $botToken; + } + + return new CommandResult($this->core->call('imbot.v2.Command.register', $apiParams)); + } + + /** + * Update an existing slash command. + * + * @param array $fields Fields to update. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-update.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Command.update', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-update.html', + 'Update an existing slash command' + )] + public function update( + int $botId, + int $commandId, + array $fields, + ?string $botToken = null, + ): CommandResult { + $params = [ + 'botId' => $botId, + 'commandId' => $commandId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new CommandResult($this->core->call('imbot.v2.Command.update', $params)); + } + + /** + * Get the list of commands registered for a bot. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-list.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Command.list', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-list.html', + 'Get the list of commands registered for a bot' + )] + public function list( + int $botId, + ?string $botToken = null, + ): CommandsResult { + $params = ['botId' => $botId]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new CommandsResult($this->core->call('imbot.v2.Command.list', $params)); + } + + /** + * Unregister (delete) a slash command. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-unregister.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Command.unregister', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-unregister.html', + 'Unregister a slash command' + )] + public function unregister( + int $botId, + int $commandId, + ?string $botToken = null, + ): EmptyResult { + $params = [ + 'botId' => $botId, + 'commandId' => $commandId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new EmptyResult($this->core->call('imbot.v2.Command.unregister', $params)); + } + + /** + * Answer a command invocation with a message. + * + * @param array|null $attach Attachments. + * @param array|null $keyboard Keyboard. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-answer.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Command.answer', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/commands/command-answer.html', + 'Answer a command invocation with a message' + )] + public function answer( + int $botId, + int $messageId, + string $dialogId, + ?string $message = null, + ?array $attach = null, + ?array $keyboard = null, + bool $system = false, + bool $urlPreview = true, + ?string $botToken = null, + ): EmptyResult { + $fields = [ + 'system' => $system, + 'urlPreview' => $urlPreview, + ]; + + if ($message !== null) { + $fields['message'] = $message; + } + + if ($attach !== null) { + $fields['attach'] = $attach; + } + + if ($keyboard !== null) { + $fields['keyboard'] = $keyboard; + } + + $params = [ + 'botId' => $botId, + 'messageId' => $messageId, + 'dialogId' => $dialogId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new EmptyResult($this->core->call('imbot.v2.Command.answer', $params)); + } +} diff --git a/src/Services/IMBot/Event/Result/EventItemResult.php b/src/Services/IMBot/Event/Result/EventItemResult.php new file mode 100644 index 00000000..77de5d3e --- /dev/null +++ b/src/Services/IMBot/Event/Result/EventItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Event\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; +use Carbon\CarbonImmutable; + +/** + * A single event item returned by imbot.v2.Event.get. + * + * @property-read int $eventId Event ID; pass as `offset` in the next call to acknowledge + * @property-read string $type Event type (e.g. ONIMBOTV2MESSAGEADD) + * @property-read ?CarbonImmutable $date Date and time of the event + * @property-read array $data Event payload; structure depends on the event type + */ +class EventItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/Event/Result/EventsResult.php b/src/Services/IMBot/Event/Result/EventsResult.php new file mode 100644 index 00000000..74630d95 --- /dev/null +++ b/src/Services/IMBot/Event/Result/EventsResult.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Event\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Event.get. + * + * Response shape: + * { + * result: { + * events: [{ eventId, type, date, data }], + * nextOffset: int, + * hasMore: bool + * } + * } + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/events/event-get.html + */ +class EventsResult extends AbstractResult +{ + /** + * Returns all events from the current page. + * + * @return list + * + * @throws BaseException + */ + public function getEvents(): array + { + $events = $this->getCoreResponse()->getResponseData()->getResult()['events'] ?? []; + + return array_map( + static fn (array $event): EventItemResult => new EventItemResult($event), + $events + ); + } + + /** + * Returns the offset to pass to the next imbot.v2.Event.get call to acknowledge processed events. + * Returns null when there are no events. + * + * @throws BaseException + */ + public function getNextOffset(): ?int + { + $value = $this->getCoreResponse()->getResponseData()->getResult()['nextOffset'] ?? null; + + return $value !== null ? (int)$value : null; + } + + /** + * Returns true when there are more unprocessed events in the queue. + * + * @throws BaseException + */ + public function isHasMore(): bool + { + return (bool)($this->getCoreResponse()->getResponseData()->getResult()['hasMore'] ?? false); + } +} diff --git a/src/Services/IMBot/Event/Service/Event.php b/src/Services/IMBot/Event/Service/Event.php new file mode 100644 index 00000000..36d2da9d --- /dev/null +++ b/src/Services/IMBot/Event/Service/Event.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Event\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Event\Result\EventsResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class Event extends AbstractService +{ + /** + * Poll pending events for the bot (fetch mode). + * + * On the first call omit $offset. Pass the returned nextOffset in subsequent calls + * to acknowledge already-processed events and move the queue cursor forward. + * + * @param int $botId Bot ID. + * @param int|null $offset Acknowledges all events with ID < this value. Omit on the first call. + * @param int|null $limit Maximum events to return (1–1000). Default: 100. + * @param bool $withUserEvents Include user events (ONIMV2*) together with bot events. + * Requires `im` scope and a prior im.v2.Event.subscribe call. + * @param string|null $botToken Bot auth token (required for webhook auth, not needed for OAuth). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/events/event-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Event.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/events/event-get.html', + 'Poll pending events for the bot' + )] + public function get( + int $botId, + ?int $offset = null, + ?int $limit = null, + bool $withUserEvents = false, + ?string $botToken = null, + ): EventsResult { + $params = ['botId' => $botId]; + + if ($offset !== null) { + $params['offset'] = $offset; + } + + if ($limit !== null) { + $params['limit'] = $limit; + } + + if ($withUserEvents) { + $params['withUserEvents'] = true; + } + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new EventsResult($this->core->call('imbot.v2.Event.get', $params)); + } +} diff --git a/src/Services/IMBot/File/Result/FileDownloadResult.php b/src/Services/IMBot/File/Result/FileDownloadResult.php new file mode 100644 index 00000000..dddce765 --- /dev/null +++ b/src/Services/IMBot/File/Result/FileDownloadResult.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\File\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.File.download. + * + * Response shape: { result: { downloadUrl: string } } + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/files/file-download.html + */ +class FileDownloadResult extends AbstractResult +{ + /** + * Returns a one-time download URL for the requested file. + * + * @throws BaseException + */ + public function getDownloadUrl(): string + { + return (string)$this->getCoreResponse()->getResponseData()->getResult()['downloadUrl']; + } +} diff --git a/src/Services/IMBot/File/Result/FileItemResult.php b/src/Services/IMBot/File/Result/FileItemResult.php new file mode 100644 index 00000000..9faaf4d6 --- /dev/null +++ b/src/Services/IMBot/File/Result/FileItemResult.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\File\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * File item returned by imbot.v2.File.upload. + * + * @property-read int $id + * @property-read int $chatId + * @property-read string $name + * @property-read string $extension + * @property-read int $size + */ +class FileItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/File/Result/FileUploadResult.php b/src/Services/IMBot/File/Result/FileUploadResult.php new file mode 100644 index 00000000..843e7afc --- /dev/null +++ b/src/Services/IMBot/File/Result/FileUploadResult.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\File\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.File.upload. + * + * Response shape: { result: { file: {...}, messageId: int, chatId: int, dialogId: string } } + */ +class FileUploadResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function file(): FileItemResult + { + return new FileItemResult($this->getCoreResponse()->getResponseData()->getResult()['file']); + } + + /** + * @throws BaseException + */ + public function getMessageId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['messageId']; + } + + /** + * @throws BaseException + */ + public function getChatId(): int + { + return (int)$this->getCoreResponse()->getResponseData()->getResult()['chatId']; + } + + /** + * @throws BaseException + */ + public function getDialogId(): string + { + return (string)$this->getCoreResponse()->getResponseData()->getResult()['dialogId']; + } +} diff --git a/src/Services/IMBot/File/Service/File.php b/src/Services/IMBot/File/Service/File.php new file mode 100644 index 00000000..1a387e70 --- /dev/null +++ b/src/Services/IMBot/File/Service/File.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\File\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\File\Result\FileDownloadResult; +use Bitrix24\SDK\Services\IMBot\File\Result\FileUploadResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class File extends AbstractService +{ + /** + * Upload a file to a chat on behalf of the bot. + * + * Combines disk upload, chat attachment, and message sending in a single call. + * + * @param string $name File name with extension. + * @param string $content Base64-encoded file content (max 100 MB). + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/files/file-upload.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.File.upload', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/files/file-upload.html', + 'Upload a file to a chat on behalf of the bot' + )] + public function upload( + int $botId, + string $dialogId, + string $name, + string $content, + ?string $message = null, + ?string $botToken = null, + ): FileUploadResult { + $fields = [ + 'name' => $name, + 'content' => $content, + ]; + + if ($message !== null) { + $fields['message'] = $message; + } + + $params = [ + 'botId' => $botId, + 'dialogId' => $dialogId, + 'fields' => $fields, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new FileUploadResult($this->core->call('imbot.v2.File.upload', $params)); + } + + /** + * Get a download URL for a file in a chat. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/files/file-download.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.File.download', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/files/file-download.html', + 'Get a download URL for a file in a chat' + )] + public function download( + int $botId, + int $fileId, + ?string $botToken = null, + ): FileDownloadResult { + $params = [ + 'botId' => $botId, + 'fileId' => $fileId, + ]; + + if ($botToken !== null) { + $params['botToken'] = $botToken; + } + + return new FileDownloadResult($this->core->call('imbot.v2.File.download', $params)); + } +} diff --git a/src/Services/IMBot/IMBotServiceBuilder.php b/src/Services/IMBot/IMBotServiceBuilder.php new file mode 100644 index 00000000..76c64661 --- /dev/null +++ b/src/Services/IMBot/IMBotServiceBuilder.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot; + +use Bitrix24\SDK\Attributes\ApiServiceBuilderMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Services\AbstractServiceBuilder; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Services\IMBot\Chat\Service\Chat; +use Bitrix24\SDK\Services\IMBot\Chat\Service\ChatInputAction; +use Bitrix24\SDK\Services\IMBot\Chat\Service\ChatManager; +use Bitrix24\SDK\Services\IMBot\Chat\Service\ChatTextField; +use Bitrix24\SDK\Services\IMBot\Chat\Service\ChatUser; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Service\Batch as ChatMessageBatch; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Service\ChatMessage; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Service\ChatMessageReaction; +use Bitrix24\SDK\Services\IMBot\Command\Service\Command; +use Bitrix24\SDK\Services\IMBot\Event\Service\Event; +use Bitrix24\SDK\Services\IMBot\File\Service\File; +use Bitrix24\SDK\Services\IMBot\Revision\Service\Revision; + +/** + * Service builder for the imbot scope. + * + * Provides access to all imbot.v2.* REST API services. + * + * @see https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/index.html + */ +#[ApiServiceBuilderMetadata(new Scope(['imbot']))] +class IMBotServiceBuilder extends AbstractServiceBuilder +{ + /** + * Bot management: imbot.v2.Bot.register, imbot.v2.Bot.update, imbot.v2.Bot.get, + * imbot.v2.Bot.list, imbot.v2.Bot.unregister. + */ + public function bot(): Bot + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Bot($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Chat management: imbot.v2.Chat.add, imbot.v2.Chat.get, imbot.v2.Chat.update, + * imbot.v2.Chat.leave, imbot.v2.Chat.setOwner. + */ + public function chat(): Chat + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Chat($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Chat users: imbot.v2.Chat.User.add, imbot.v2.Chat.User.delete, imbot.v2.Chat.User.list. + */ + public function chatUser(): ChatUser + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatUser($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Chat managers: imbot.v2.Chat.Manager.add, imbot.v2.Chat.Manager.delete. + */ + public function chatManager(): ChatManager + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatManager($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Chat messages: imbot.v2.Chat.Message.send, imbot.v2.Chat.Message.update, + * imbot.v2.Chat.Message.delete, imbot.v2.Chat.Message.read, + * imbot.v2.Chat.Message.get, imbot.v2.Chat.Message.getContext. + * Batch: send, delete, update. + */ + public function chatMessage(): ChatMessage + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatMessage( + new ChatMessageBatch($this->batch, $this->log), + $this->core, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Message reactions: imbot.v2.Chat.Message.Reaction.add, imbot.v2.Chat.Message.Reaction.delete. + */ + public function chatMessageReaction(): ChatMessageReaction + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatMessageReaction($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Input action: imbot.v2.Chat.InputAction.notify. + */ + public function chatInputAction(): ChatInputAction + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatInputAction($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Text field: imbot.v2.Chat.TextField.enabled. + */ + public function chatTextField(): ChatTextField + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new ChatTextField($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Commands: imbot.v2.Command.register, imbot.v2.Command.update, imbot.v2.Command.list, + * imbot.v2.Command.unregister, imbot.v2.Command.answer. + */ + public function command(): Command + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Command($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Bot events (fetch mode): imbot.v2.Event.get. + */ + public function event(): Event + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Event($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * File operations: imbot.v2.File.upload, imbot.v2.File.download. + */ + public function file(): File + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new File($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } + + /** + * Revision info: imbot.v2.Revision.get. + */ + public function revision(): Revision + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new Revision($this->core, $this->log); + } + + return $this->serviceCache[__METHOD__]; + } +} diff --git a/src/Services/IMBot/Revision/Result/RevisionItemResult.php b/src/Services/IMBot/Revision/Result/RevisionItemResult.php new file mode 100644 index 00000000..b9498f6d --- /dev/null +++ b/src/Services/IMBot/Revision/Result/RevisionItemResult.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Revision\Result; + +use Bitrix24\SDK\Core\Result\AbstractItem; + +/** + * Revision numbers returned by imbot.v2.Revision.get. + * + * @property-read int $rest + * @property-read int $web + * @property-read int $mobile + * @property-read int $desktop + */ +class RevisionItemResult extends AbstractItem +{ +} diff --git a/src/Services/IMBot/Revision/Result/RevisionResult.php b/src/Services/IMBot/Revision/Result/RevisionResult.php new file mode 100644 index 00000000..7009eafb --- /dev/null +++ b/src/Services/IMBot/Revision/Result/RevisionResult.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Revision\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Result\AbstractResult; + +/** + * Result for imbot.v2.Revision.get. + */ +class RevisionResult extends AbstractResult +{ + /** + * @throws BaseException + */ + public function revision(): RevisionItemResult + { + return new RevisionItemResult($this->getCoreResponse()->getResponseData()->getResult()); + } +} diff --git a/src/Services/IMBot/Revision/Service/Revision.php b/src/Services/IMBot/Revision/Service/Revision.php new file mode 100644 index 00000000..bdb84f3d --- /dev/null +++ b/src/Services/IMBot/Revision/Service/Revision.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\IMBot\Revision\Service; + +use Bitrix24\SDK\Attributes\ApiEndpointMetadata; +use Bitrix24\SDK\Attributes\ApiServiceMetadata; +use Bitrix24\SDK\Core\Credentials\Scope; +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\IMBot\Revision\Result\RevisionResult; + +#[ApiServiceMetadata(new Scope(['imbot']))] +class Revision extends AbstractService +{ + /** + * Get REST API and client protocol revision numbers. + * + * @link https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/revision-get.html + * + * @throws BaseException + * @throws TransportException + */ + #[ApiEndpointMetadata( + 'imbot.v2.Revision.get', + 'https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/imbot.v2/revision-get.html', + 'Get REST API and client protocol revision numbers' + )] + public function get(): RevisionResult + { + return new RevisionResult($this->core->call('imbot.v2.Revision.get')); + } +} diff --git a/src/Services/ServiceBuilder.php b/src/Services/ServiceBuilder.php index 4c1026e5..13a9507f 100644 --- a/src/Services/ServiceBuilder.php +++ b/src/Services/ServiceBuilder.php @@ -24,6 +24,7 @@ use Bitrix24\SDK\Services\Department\DepartmentServiceBuilder; use Bitrix24\SDK\Services\Task\TaskServiceBuilder; use Bitrix24\SDK\Services\IM\IMServiceBuilder; +use Bitrix24\SDK\Services\IMBot\IMBotServiceBuilder; use Bitrix24\SDK\Services\IMOpenLines\IMOpenLinesServiceBuilder; use Bitrix24\SDK\Services\Log\LogServiceBuilder; use Bitrix24\SDK\Services\Main\MainServiceBuilder; @@ -162,6 +163,20 @@ public function getIMScope(): IMServiceBuilder return $this->serviceCache[__METHOD__]; } + public function getIMBotScope(): IMBotServiceBuilder + { + if (!isset($this->serviceCache[__METHOD__])) { + $this->serviceCache[__METHOD__] = new IMBotServiceBuilder( + $this->core, + $this->batch, + $this->bulkItemsReader, + $this->log + ); + } + + return $this->serviceCache[__METHOD__]; + } + public function getIMOpenLinesScope(): IMOpenLinesServiceBuilder { if (!isset($this->serviceCache[__METHOD__])) { diff --git a/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php b/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php new file mode 100644 index 00000000..8fa95fc2 --- /dev/null +++ b/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Chat\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatEntityType; +use Bitrix24\SDK\Services\IM\Chat\Result\ChatItemResult; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ChatItemResult::class)] +class ChatItemResultTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Chat $chatService; + + private int $createdChatId = 0; + + private string $entityId = ''; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $this->chatService = Factory::getServiceBuilder()->getIMScope()->chat(); + + $currentUserId = (int)$this->chatService->core + ->call('PROFILE')->getResponseData()->getResult()['ID']; + + $this->entityId = sprintf('ANNOTATION_%s', uniqid('', true)); + $this->createdChatId = $this->chatService->add( + users: [$currentUserId], + title: sprintf('Annotation test chat %s', uniqid('', true)), + chatEntityType: ChatEntityType::Calendar, + entityId: $this->entityId, + )->getId(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + if ($this->createdChatId > 0) { + try { + $this->chatService->leave($this->createdChatId); + } catch (BaseException) { + // ignore: best-effort cleanup + } + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in ChatItemResult are annotated in phpdoc and match with raw api response')] + public function testAllFieldsAreAnnotated(): void + { + $rawItem = $this->chatService->get(ChatEntityType::Calendar, $this->entityId) + ->getCoreResponse()->getResponseData()->getResult(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItem), + ChatItemResult::class, + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in ChatItemResult have valid type casting in magic getters')] + public function testAllFieldsHasValidTypeCastingInMagicGetters(): void + { + $chatItem = $this->chatService->get(ChatEntityType::Calendar, $this->entityId)->chat(); + + $this->assertNotNull($chatItem); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $chatItem, + ChatItemResult::class, + ); + } +} diff --git a/tests/Integration/Services/IM/Chat/Service/ChatTest.php b/tests/Integration/Services/IM/Chat/Service/ChatTest.php new file mode 100644 index 00000000..151f89d3 --- /dev/null +++ b/tests/Integration/Services/IM/Chat/Service/ChatTest.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Chat\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatColor; +use Bitrix24\SDK\Services\IM\Chat\ChatEntityType; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Chat::class)] +class ChatTest extends TestCase +{ + private Chat $chatService; + + private int $currentUserId = 0; + + /** + * @var list + */ + private array $createdChats = []; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $this->chatService = Factory::getServiceBuilder()->getIMScope()->chat(); + $this->currentUserId = (int)$this->chatService->core + ->call('PROFILE')->getResponseData()->getResult()['ID']; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->createdChats as $createdChat) { + try { + $this->chatService->leave($createdChat); + } catch (BaseException) { + // chat may already be left by the test itself + } + } + + $this->createdChats = []; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.add returns a positive chat id')] + public function testAdd(): void + { + $chatId = $this->createChat(); + $this->assertGreaterThan(0, $chatId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.get returns the chat id matched by entity type and id')] + public function testGet(): void + { + $entityId = sprintf('GET_%s', uniqid('', true)); + $chatId = $this->createChat(chatEntityType: ChatEntityType::Calendar, entityId: $entityId); + + $result = $this->chatService->get(ChatEntityType::Calendar, $entityId)->chat(); + + $this->assertNotNull($result); + $this->assertSame($chatId, $result->ID); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.get returns null when no chat matches the entity')] + public function testGetReturnsNullForUnknownEntity(): void + { + $entityId = sprintf('MISSING_%s', uniqid('', true)); + + $result = $this->chatService->get(ChatEntityType::Calendar, $entityId)->chat(); + + $this->assertNull($result); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.leave returns success for the current user')] + public function testLeave(): void + { + $chatId = $this->createChat(); + + $updatedItemResult = $this->chatService->leave($chatId); + + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.mute toggles notifications by chat id')] + public function testMute(): void + { + $chatId = $this->createChat(); + + $this->assertTrue($this->chatService->mute($chatId, true)->isSuccess()); + $this->assertTrue($this->chatService->mute($chatId, false)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.mute toggles notifications by dialog id')] + public function testMuteByDialog(): void + { + $chatId = $this->createChat(); + $dialogId = sprintf('chat%d', $chatId); + + $this->assertTrue($this->chatService->muteByDialog($dialogId, true)->isSuccess()); + $this->assertTrue($this->chatService->muteByDialog($dialogId, false)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.updateAvatar returns success for a valid base64 payload')] + public function testUpdateAvatar(): void + { + $chatId = $this->createChat(); + // 1x1 transparent PNG + $avatar = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII='; + + $updatedItemResult = $this->chatService->updateAvatar($chatId, $avatar); + + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.updateColor returns success for every supported chat color')] + public function testUpdateColor(): void + { + $chatId = $this->createChat(); + + foreach ([ChatColor::Red, ChatColor::Graphite] as $chatColor) { + $this->assertTrue($this->chatService->updateColor($chatId, $chatColor)->isSuccess()); + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.updateTitle returns success with a unique title')] + public function testUpdateTitle(): void + { + $chatId = $this->createChat(); + $title = sprintf('Updated %s', uniqid('', true)); + + $updatedItemResult = $this->chatService->updateTitle($chatId, $title); + + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.setOwner transfers chat ownership to another member')] + public function testSetOwner(): void + { + $otherUserId = $this->findAnotherUserId(); + if ($otherUserId === null) { + $this->markTestSkipped('Portal has no second user to transfer ownership to.'); + } + + $chatId = $this->createChat([$this->currentUserId, $otherUserId]); + + $updatedItemResult = $this->chatService->setOwner($chatId, $otherUserId); + + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @param list|null $users + * + * @throws BaseException + * @throws TransportException + */ + private function createChat( + ?array $users = null, + ?ChatEntityType $chatEntityType = null, + ?string $entityId = null, + ): int { + $chatId = $this->chatService->add( + users: $users ?? [$this->currentUserId], + chatType: ChatType::Closed, + title: sprintf('IT chat %s', uniqid('', true)), + chatEntityType: $chatEntityType, + entityId: $entityId, + )->getId(); + + $this->createdChats[] = $chatId; + + return $chatId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function findAnotherUserId(): ?int + { + $response = $this->chatService->core->call('user.get', [ + 'FILTER' => ['ACTIVE' => true], + ])->getResponseData()->getResult(); + + foreach ($response as $user) { + $id = (int)($user['ID'] ?? 0); + if ($id > 0 && $id !== $this->currentUserId) { + return $id; + } + } + + return null; + } +} diff --git a/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php b/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php new file mode 100644 index 00000000..f46630a4 --- /dev/null +++ b/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Chat\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Services\IM\Chat\Service\ChatUser; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(ChatUser::class)] +class ChatUserTest extends TestCase +{ + private Chat $chatService; + + private ChatUser $chatUserService; + + private int $currentUserId = 0; + + /** + * @var list + */ + private array $createdChats = []; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $imServiceBuilder = Factory::getServiceBuilder()->getIMScope(); + $this->chatService = $imServiceBuilder->chat(); + $this->chatUserService = $imServiceBuilder->chatUser(); + $this->currentUserId = (int)$this->chatUserService->core + ->call('PROFILE')->getResponseData()->getResult()['ID']; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->createdChats as $createdChat) { + try { + $this->chatService->leave($createdChat); + } catch (BaseException) { + // chat may already be left by the test itself + } + } + + $this->createdChats = []; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.user.list returns participant user IDs for a chat')] + public function testList(): void + { + $otherUserId = $this->findAnotherUserId(); + if ($otherUserId === null) { + $this->markTestSkipped('Portal has no second user to test participant list.'); + } + + $chatId = $this->createChat([$this->currentUserId, $otherUserId]); + + $userIds = $this->chatUserService->list($chatId)->getUserIds(); + + $this->assertContains($this->currentUserId, $userIds); + $this->assertContains($otherUserId, $userIds); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.user.add adds a new participant to a chat')] + public function testAdd(): void + { + $otherUserId = $this->findAnotherUserId(); + if ($otherUserId === null) { + $this->markTestSkipped('Portal has no second user to add to a chat.'); + } + + $chatId = $this->createChat([$this->currentUserId]); + + $updatedItemResult = $this->chatUserService->add($chatId, [$otherUserId]); + + $this->assertTrue($updatedItemResult->isSuccess()); + $this->assertContains($otherUserId, $this->chatUserService->list($chatId)->getUserIds()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.chat.user.delete removes a participant from a chat')] + public function testDelete(): void + { + $otherUserId = $this->findAnotherUserId(); + if ($otherUserId === null) { + $this->markTestSkipped('Portal has no second user to remove from a chat.'); + } + + $chatId = $this->createChat([$this->currentUserId, $otherUserId]); + + $updatedItemResult = $this->chatUserService->delete($chatId, $otherUserId); + + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @param list|null $users + * + * @throws BaseException + * @throws TransportException + */ + private function createChat(?array $users = null): int + { + $chatId = $this->chatService->add( + users: $users ?? [$this->currentUserId], + chatType: ChatType::Closed, + title: sprintf('IT ChatUser %s', uniqid('', true)), + )->getId(); + + $this->createdChats[] = $chatId; + + return $chatId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function findAnotherUserId(): ?int + { + $response = $this->chatUserService->core->call('user.get', [ + 'FILTER' => ['ACTIVE' => true], + ])->getResponseData()->getResult(); + + foreach ($response as $user) { + $id = (int)($user['ID'] ?? 0); + if ($id > 0 && $id !== $this->currentUserId) { + return $id; + } + } + + return null; + } +} diff --git a/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php b/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php new file mode 100644 index 00000000..85378fd4 --- /dev/null +++ b/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Counters\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Counters\Result\CountersItemResult; +use Bitrix24\SDK\Services\IM\Counters\Service\Counters; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(CountersItemResult::class)] +class CountersItemResultTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Counters $countersService; + + #[\Override] + protected function setUp(): void + { + $this->countersService = Factory::getServiceBuilder()->getIMScope()->counters(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in CountersItemResult are annotated in phpdoc and match with raw api response')] + public function testAllFieldsAreAnnotated(): void + { + $rawItem = $this->countersService->get() + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItem), + CountersItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in CountersItemResult have valid type casting in magic getters')] + public function testAllFieldsHasValidTypeCastingInMagicGetters(): void + { + $countersItemResult = $this->countersService->get()->counters(); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $countersItemResult, + CountersItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Counters/Service/CountersTest.php b/tests/Integration/Services/IM/Counters/Service/CountersTest.php new file mode 100644 index 00000000..61750bf4 --- /dev/null +++ b/tests/Integration/Services/IM/Counters/Service/CountersTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Counters\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Counters\Service\Counters; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Counters::class)] +class CountersTest extends TestCase +{ + private Counters $countersService; + + #[\Override] + protected function setUp(): void + { + $this->countersService = Factory::getServiceBuilder()->getIMScope()->counters(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.counters.get returns a CountersItemResult with valid counter values')] + public function testGet(): void + { + $counters = $this->countersService->get()->counters(); + + $this->assertIsArray($counters->TYPE); + $this->assertIsArray($counters->CHAT); + $this->assertIsArray($counters->CHAT_MUTED); + $this->assertIsArray($counters->CHAT_UNREAD); + $this->assertIsArray($counters->LINES); + $this->assertIsArray($counters->DIALOG); + $this->assertIsArray($counters->DIALOG_UNREAD); + } +} diff --git a/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php new file mode 100644 index 00000000..9edeb51d --- /dev/null +++ b/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Department\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Department\Result\DepartmentItemResult; +use Bitrix24\SDK\Services\IM\Department\Service\Department; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(DepartmentItemResult::class)] +final class DepartmentItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Department $departmentService; + + #[\Override] + protected function setUp(): void + { + $this->departmentService = Factory::getServiceBuilder()->getIMScope()->department(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in DepartmentItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->departmentService->get([1], true) + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + if ($rawResult === []) { + $this->markTestSkipped('No department payload available to validate annotations'); + } + + $firstItem = reset($rawResult); + if (!is_array($firstItem)) { + $this->markTestSkipped('Unexpected response shape for im.department.get'); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($firstItem), + DepartmentItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in DepartmentItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $items = $this->departmentService->get([1], true)->items(); + + if ($items === []) { + $this->markTestSkipped('No department payload available to validate type casting'); + } + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + DepartmentItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Department/Service/DepartmentTest.php b/tests/Integration/Services/IM/Department/Service/DepartmentTest.php new file mode 100644 index 00000000..21ea52e3 --- /dev/null +++ b/tests/Integration/Services/IM/Department/Service/DepartmentTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Department\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Department\Result\DepartmentItemResult; +use Bitrix24\SDK\Services\IM\Department\Service\Department; +use Bitrix24\SDK\Services\IM\User\Result\UserItemResult; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Department::class)] +final class DepartmentTest extends TestCase +{ + private Department $departmentService; + + #[\Override] + protected function setUp(): void + { + $this->departmentService = Factory::getServiceBuilder()->getIMScope()->department(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.department.get returns a list of DepartmentItemResult')] + public function testGet(): void + { + $items = $this->departmentService->get([1], true)->items(); + + if ($items === []) { + $this->markTestSkipped('No department 1 payload available for im.department.get'); + } + + $this->assertInstanceOf(DepartmentItemResult::class, $items[0]); + $this->assertSame(1, $items[0]->id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.department.colleagues.list returns a paginated list of UserItemResult')] + public function testColleaguesList(): void + { + $departmentUsersResult = $this->departmentService->colleaguesList(userData: true, limit: 3); + $users = $departmentUsersResult->users(); + + $this->assertIsArray($users); + $this->assertGreaterThanOrEqual(0, $departmentUsersResult->total()); + $this->assertTrue($departmentUsersResult->next() === null || $departmentUsersResult->next() >= 0); + + if ($users === []) { + $this->markTestSkipped('No colleagues available for im.department.colleagues.list'); + } + + $this->assertInstanceOf(UserItemResult::class, $users[0]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.department.employees.get returns users grouped by department ID')] + public function testEmployeesGet(): void + { + $usersByDepartment = $this->departmentService->employeesGet([1], true)->usersByDepartment(); + + $this->assertIsArray($usersByDepartment); + + if (($usersByDepartment[1] ?? []) === []) { + $this->markTestSkipped('No users available in department 1 for im.department.employees.get'); + } + + $this->assertInstanceOf(UserItemResult::class, $usersByDepartment[1][0]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.department.managers.get returns users grouped by department ID or an empty result')] + public function testManagersGet(): void + { + $usersByDepartment = $this->departmentService->managersGet([1], true)->usersByDepartment(); + + $this->assertIsArray($usersByDepartment); + + if (($usersByDepartment[1] ?? []) !== []) { + $this->assertInstanceOf(UserItemResult::class, $usersByDepartment[1][0]); + } + } +} diff --git a/tests/Integration/Services/IM/Dialog/Result/DialogItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Dialog/Result/DialogItemResultAnnotationsTest.php new file mode 100644 index 00000000..4ec93448 --- /dev/null +++ b/tests/Integration/Services/IM/Dialog/Result/DialogItemResultAnnotationsTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogItemResult; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Service\DialogChatTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; + +#[CoversClass(DialogItemResult::class)] +#[CoversMethod(Dialog::class, 'get')] +final class DialogItemResultAnnotationsTest extends DialogChatTestCase +{ + use CustomBitrix24Assertions; + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsAnnotated(): void + { + $dialogItemResult = $this->getDialogItem(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys(iterator_to_array($dialogItemResult)), + DialogItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $this->getDialogItem(), + DialogItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function getDialogItem(): DialogItemResult + { + $dialog = $this->dialogService->get( + $this->createDialogId($this->createChat()) + )->dialog(); + + self::assertInstanceOf(DialogItemResult::class, $dialog); + + return $dialog; + } +} diff --git a/tests/Integration/Services/IM/Dialog/Result/DialogUserItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Dialog/Result/DialogUserItemResultAnnotationsTest.php new file mode 100644 index 00000000..addb8ba1 --- /dev/null +++ b/tests/Integration/Services/IM/Dialog/Result/DialogUserItemResultAnnotationsTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Dialog\Result\DialogUserItemResult; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Service\DialogChatTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; + +#[CoversClass(DialogUserItemResult::class)] +#[CoversMethod(Dialog::class, 'usersList')] +final class DialogUserItemResultAnnotationsTest extends DialogChatTestCase +{ + use CustomBitrix24Assertions; + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsAnnotated(): void + { + $dialogUserItemResult = $this->getDialogUserItem(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys(iterator_to_array($dialogUserItemResult)), + DialogUserItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $this->getDialogUserItem(), + DialogUserItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function getDialogUserItem(): DialogUserItemResult + { + $user = $this->dialogService->usersList( + $this->createDialogId($this->createChat()), + limit: 50 + )->users()[0] ?? null; + + self::assertInstanceOf(DialogUserItemResult::class, $user); + + return $user; + } +} diff --git a/tests/Integration/Services/IM/Dialog/Result/MessageItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Dialog/Result/MessageItemResultAnnotationsTest.php new file mode 100644 index 00000000..783cb9ae --- /dev/null +++ b/tests/Integration/Services/IM/Dialog/Result/MessageItemResultAnnotationsTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Dialog\Result\MessageItemResult; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Service\DialogChatTestCase; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\CoversMethod; + +#[CoversClass(MessageItemResult::class)] +#[CoversMethod(Dialog::class, 'messagesGet')] +#[CoversMethod(Dialog::class, 'messagesSearch')] +final class MessageItemResultAnnotationsTest extends DialogChatTestCase +{ + use CustomBitrix24Assertions; + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsAnnotated(): void + { + [$messagesGetItem, $messagesSearchItem] = $this->getMessageItems(); + $fieldCodes = array_values(array_unique(array_merge( + array_keys(iterator_to_array($messagesGetItem)), + array_keys(iterator_to_array($messagesSearchItem)) + ))); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + $fieldCodes, + MessageItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + [$messagesGetItem, $messagesSearchItem] = $this->getMessageItems(); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $messagesGetItem, + MessageItemResult::class + ); + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $messagesSearchItem, + MessageItemResult::class + ); + } + + /** + * @return array{MessageItemResult, MessageItemResult} + * + * @throws BaseException + * @throws TransportException + */ + private function getMessageItems(): array + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + $needle = sprintf('annotation-%s', uniqid('', true)); + $this->seedMessages($dialogId, [$needle]); + + $messagesGetItem = $this->dialogService->messagesGet($dialogId, null, null, 10)->messages()[0] ?? null; + $messagesSearchItem = $this->dialogService->messagesSearch($chatId, 'annotation', limit: 20)->messages()[0] ?? null; + + self::assertInstanceOf(MessageItemResult::class, $messagesGetItem); + self::assertInstanceOf(MessageItemResult::class, $messagesSearchItem); + + return [$messagesGetItem, $messagesSearchItem]; + } +} diff --git a/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php b/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php new file mode 100644 index 00000000..d2896d4a --- /dev/null +++ b/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\TestCase; + +abstract class DialogChatTestCase extends TestCase +{ + protected Dialog $dialogService; + + protected Chat $chatService; + + protected Message $messageService; + + protected int $currentUserId; + + /** + * @var list + */ + private array $createdChats = []; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $imServiceBuilder = Factory::getServiceBuilder()->getIMScope(); + $this->dialogService = $imServiceBuilder->dialog(); + $this->chatService = $imServiceBuilder->chat(); + $this->messageService = $imServiceBuilder->message(); + $this->currentUserId = (int)$this->dialogService->core + ->call('PROFILE') + ->getResponseData() + ->getResult()['ID']; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->createdChats as $createdChat) { + try { + $this->chatService->leave($createdChat); + } catch (BaseException) { + // chat may already be left by the test itself + } + } + + $this->createdChats = []; + } + + /** + * @throws BaseException + * @throws TransportException + */ + protected function createChat(): int + { + $chatId = $this->chatService->add( + users: [$this->currentUserId], + chatType: ChatType::Closed, + title: sprintf('Dialog test %s', uniqid('', true)), + )->getId(); + + $this->createdChats[] = $chatId; + + return $chatId; + } + + protected function createDialogId(int $chatId): string + { + return sprintf('chat%d', $chatId); + } + + /** + * @param list $messages + * + * @return list + * + * @throws BaseException + * @throws TransportException + */ + protected function seedMessages(string $dialogId, array $messages): array + { + $messageIds = []; + + foreach ($messages as $message) { + $messageIds[] = $this->messageService->add( + dialogId: $dialogId, + message: $message, + )->getId(); + } + + return $messageIds; + } +} diff --git a/tests/Integration/Services/IM/Dialog/Service/DialogTest.php b/tests/Integration/Services/IM/Dialog/Service/DialogTest.php new file mode 100644 index 00000000..15a6512c --- /dev/null +++ b/tests/Integration/Services/IM/Dialog/Service/DialogTest.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Dialog\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Services\IM\Dialog\Service\Dialog; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use Bitrix24\SDK\Tests\Integration\Fabric; +use Carbon\CarbonImmutable; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Dialog::class)] +final class DialogTest extends DialogChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.get returns dialog information for a created chat')] + public function testGet(): void + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + + $dialog = $this->dialogService->get($dialogId)->dialog(); + + $this->assertNotNull($dialog); + $this->assertSame($dialogId, $dialog->dialog_id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.messages.get returns seeded dialog messages')] + public function testMessagesGet(): void + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + $messageIds = $this->seedMessages($dialogId, [ + sprintf('Dialog get seed %s', uniqid('', true)), + sprintf('Dialog get seed %s', uniqid('', true)), + ]); + + $dialogMessagesResult = $this->dialogService->messagesGet($dialogId, null, null, 10); + + $messageIdsFromResponse = array_map( + static fn(object $message): int => $message->id, + $dialogMessagesResult->messages() + ); + + $this->assertNotEmpty($messageIdsFromResponse); + $this->assertContains($messageIds[0], $messageIdsFromResponse); + $this->assertContains($messageIds[1], $messageIdsFromResponse); + $this->assertSame($chatId, $dialogMessagesResult->chatId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.messages.search returns messages matching the search text')] + public function testMessagesSearch(): void + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + $needle = sprintf('needle-%s', uniqid('', true)); + $dateFrom = CarbonImmutable::now()->subMinutes(5); + $this->seedMessages($dialogId, [ + sprintf('prefix %s suffix', $needle), + ]); + $dateTo = CarbonImmutable::now()->addMinutes(5); + + $dialogMessageSearchResult = $this->dialogService->messagesSearch($chatId, $needle, $dateFrom, $dateTo, limit: 20); + $texts = array_map( + static fn(object $message): string => $message->text, + $dialogMessageSearchResult->messages() + ); + + $this->assertNotEmpty($texts); + $this->assertTrue( + array_any( + $texts, + static fn(string $text): bool => str_contains($text, $needle) + ) + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.read returns the updated read state')] + public function testRead(): void + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + $messageId = $this->seedMessages($dialogId, [ + sprintf('Read state %s', uniqid('', true)), + ])[0]; + + $this->assertTrue($this->dialogService->unread($dialogId, $messageId)->isSuccess()); + + $readState = $this->dialogService->read($dialogId, $messageId)->readState(); + + $this->assertNotNull($readState); + $this->assertSame($dialogId, $readState->dialogId); + $this->assertSame($chatId, $readState->chatId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.read.all returns success')] + public function testReadAll(): void + { + $this->assertTrue($this->dialogService->readAll()->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.unread returns success for a seeded message')] + public function testUnread(): void + { + $dialogId = $this->createDialogId($this->createChat()); + $messageId = $this->seedMessages($dialogId, [ + sprintf('Unread state %s', uniqid('', true)), + ])[0]; + + $this->assertTrue($this->dialogService->unread($dialogId, $messageId)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.users.list returns participants and pagination metadata')] + public function testUsersList(): void + { + $chatId = $this->createChat(); + $dialogId = $this->createDialogId($chatId); + + $dialogUsersResult = $this->dialogService->usersList($dialogId, limit: 50); + $userIds = array_map( + static fn(object $user): int => $user->id, + $dialogUsersResult->users() + ); + + $this->assertNotEmpty($userIds); + $this->assertContains($this->currentUserId, $userIds); + $this->assertGreaterThanOrEqual(1, $dialogUsersResult->total()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.dialog.writing returns success for a dialog')] + public function testWriting(): void + { + $dialogId = $this->createDialogId($this->createChat()); + + $this->assertTrue($this->dialogService->writing($dialogId)->isSuccess()); + } +} diff --git a/tests/Integration/Services/IM/Disk/Service/DiskTest.php b/tests/Integration/Services/IM/Disk/Service/DiskTest.php new file mode 100644 index 00000000..d07ca68b --- /dev/null +++ b/tests/Integration/Services/IM/Disk/Service/DiskTest.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Disk\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\Disk\File\Service\File; +use Bitrix24\SDK\Services\Disk\Folder\Service\Folder; +use Bitrix24\SDK\Services\IM\Disk\Service\Disk; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Disk::class)] +class DiskTest extends TestCase +{ + private Disk $diskService; + + private Folder $folderService; + + private File $fileService; + + #[\Override] + protected function setUp(): void + { + $this->diskService = Factory::getServiceBuilder()->getIMScope()->disk(); + $this->folderService = Factory::getServiceBuilder()->getDiskScope()->folder(); + $this->fileService = Factory::getServiceBuilder()->getDiskScope()->file(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testGetFolderId(): void + { + $chatId = $this->createChat(); + + try { + $folderId = $this->diskService->getFolderId(dialogId: 'chat' . $chatId)->getId(); + + $this->assertGreaterThan(0, $folderId); + } finally { + $this->leaveChat($chatId); + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + public function testFileCommitSaveAndDelete(): void + { + $chatId = $this->createChat(); + $chatFileId = null; + $savedFileId = null; + + try { + $dialogId = 'chat' . $chatId; + $folderId = $this->diskService->getFolderId(dialogId: $dialogId)->getId(); + $sourceFileId = $this->uploadTinyFileToImFolder($folderId); + + $commitResult = $this->diskService->commitFile(dialogId: $dialogId, fileId: $sourceFileId); + $files = $commitResult->files(); + $fileModels = $commitResult->fileModels(); + $diskIds = $commitResult->diskIds(); + $resultKey = 'disk' . $sourceFileId; + + self::assertNotEmpty($files); + self::assertNotEmpty($fileModels); + self::assertNotEmpty($diskIds); + self::assertGreaterThan(0, $commitResult->messageId()); + self::assertArrayHasKey($resultKey, $files); + self::assertArrayHasKey($resultKey, $fileModels); + + $chatFileId = $diskIds[0]; + self::assertSame($chatFileId, (int)$files[$resultKey]['id']); + self::assertSame($chatFileId, (int)$fileModels[$resultKey]['id']); + + $saveResult = $this->diskService->saveFile($chatFileId); + $savedFileId = $saveResult->fileId(); + + self::assertGreaterThan(0, $saveResult->folderId()); + self::assertGreaterThan(0, $savedFileId); + + self::assertTrue($this->diskService->deleteFile($chatId, $chatFileId)->isSuccess()); + $chatFileId = null; + } finally { + if ($savedFileId !== null) { + $this->deleteDiskFile($savedFileId); + } + + if ($chatFileId !== null) { + $this->deleteImDiskFile($chatId, $chatFileId); + } + + $this->leaveChat($chatId); + } + } + + public function testShareRecordRequiresRecordCompatibleDiskId(): void + { + $userId = (int)$this->diskService->core->call('PROFILE')->getResponseData()->getResult()['ID']; + + $this->expectException(BaseException::class); + $this->expectExceptionMessage('execute_error - error during record share'); + + $this->diskService->shareRecord((string)$userId, 999999999); + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function createChat(): int + { + $userId = (int)$this->diskService->core->call('PROFILE')->getResponseData()->getResult()['ID']; + + return (int)$this->diskService->core->call( + 'im.chat.add', + [ + 'USERS' => [$userId], + 'TYPE' => 'CHAT', + 'TITLE' => sprintf('IM Disk Test %s', time()), + ] + )->getResponseData()->getResult()[0]; + } + + private function leaveChat(int $chatId): void + { + try { + $this->diskService->core->call('im.chat.leave', ['CHAT_ID' => $chatId]); + } catch (BaseException) { + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function uploadTinyFileToImFolder(int $folderId): int + { + return $this->folderService->uploadFile( + $folderId, + ['NAME' => sprintf('im_disk_%s.txt', time())], + base64_encode('IM Disk integration test ' . time()), + true, + )->getId(); + } + + private function deleteDiskFile(int $fileId): void + { + try { + $this->fileService->delete($fileId); + } catch (BaseException) { + } + } + + private function deleteImDiskFile(int $chatId, int $fileId): void + { + try { + $this->diskService->deleteFile($chatId, $fileId); + } catch (BaseException) { + } + } +} diff --git a/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php new file mode 100644 index 00000000..9237ab7a --- /dev/null +++ b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\EventV2\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\EventV2\Result\EventsV2Result; +use Bitrix24\SDK\Services\IM\EventV2\Service\EventV2; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(EventV2::class)] +class EventV2Test extends TestCase +{ + private EventV2 $eventService; + + #[\Override] + protected function setUp(): void + { + $this->eventService = Factory::getServiceBuilder()->getIMScope()->eventV2(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.v2.Event.subscribe returns success')] + public function testSubscribe(): void + { + $result = $this->eventService->subscribe(); + + // EmptyResult — no exception means success + $this->assertNotNull($result->getCoreResponse()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.v2.Event.unsubscribe returns success')] + public function testUnsubscribe(): void + { + $this->eventService->subscribe(); + $result = $this->eventService->unsubscribe(); + + // EmptyResult — no exception means success + $this->assertNotNull($result->getCoreResponse()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.v2.Event.get returns EventsV2Result with events, nextOffset and hasMore')] + public function testGet(): void + { + $this->eventService->subscribe(); + $result = $this->eventService->get(0); + + $this->assertInstanceOf(EventsV2Result::class, $result); + $this->assertIsArray($result->getEvents()); + $this->assertIsBool($result->isHasMore()); + // nextOffset may be null when there are no events + $nextOffset = $result->getNextOffset(); + $this->assertTrue($nextOffset === null || is_int($nextOffset)); + } +} diff --git a/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php new file mode 100644 index 00000000..85fd732b --- /dev/null +++ b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\FileV2\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\FileV2\Service\FileV2; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(FileV2::class)] +class FileV2Test extends TestCase +{ + private FileV2 $fileService; + + /** + * ID of the group chat created for the test run; cleaned up in tearDown. + */ + private int $chatId = 0; + + #[\Override] + protected function setUp(): void + { + $this->fileService = Factory::getServiceBuilder()->getIMScope()->fileV2(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + if ($this->chatId > 0) { + try { + $this->fileService->core->call('im.chat.leave', ['CHAT_ID' => $this->chatId]); + } catch (BaseException) { + // ignore — chat may already be gone + } + } + } + + /** + * Creates a temporary group chat for the current API user and returns its dialog ID (e.g. "chat5"). + * + * @throws BaseException + * @throws TransportException + */ + private function createChat(): string + { + $userId = (int)$this->fileService->core->call('PROFILE')->getResponseData()->getResult()['ID']; + + $this->chatId = (int)$this->fileService->core->call( + 'im.chat.add', + [ + 'USERS' => [$userId], + 'TYPE' => 'CHAT', + 'TITLE' => sprintf('IM FileV2 Test %s', time()), + ] + )->getResponseData()->getResult()[0]; + + return 'chat' . $this->chatId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.v2.File.upload sends a file to a chat and returns FileV2UploadResult')] + public function testUpload(): void + { + $dialogId = $this->createChat(); + + $result = $this->fileService->upload( + dialogId: $dialogId, + name: 'test.txt', + content: base64_encode('IM FileV2 integration test ' . time()), + message: 'Test file upload' + ); + + $file = $result->file(); + + $this->assertGreaterThan(0, $file->id); + $this->assertNotEmpty($file->name); + $this->assertGreaterThan(0, $file->size); + $this->assertGreaterThan(0, $result->getMessageId()); + $this->assertGreaterThan(0, $result->getChatId()); + $this->assertNotEmpty($result->getDialogId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.v2.File.download returns a one-time download URL for an uploaded file')] + public function testDownload(): void + { + $dialogId = $this->createChat(); + + // First upload a file to get a file ID + $uploadResult = $this->fileService->upload( + dialogId: $dialogId, + name: 'download_test.txt', + content: base64_encode('IM FileV2 download test ' . time()) + ); + + $fileId = $uploadResult->file()->id; + $this->assertGreaterThan(0, $fileId); + + // Now get a download URL + $downloadResult = $this->fileService->download($fileId); + + $this->assertNotEmpty($downloadResult->getDownloadUrl()); + $this->assertStringContainsString('http', $downloadResult->getDownloadUrl()); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachDelimiterBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachDelimiterBlockTest.php new file mode 100644 index 00000000..b7e9d042 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachDelimiterBlockTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachDelimiterBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts DELIMITER block with default settings')] + public function testAddAttachDelimiterBlockDefault(): void + { + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => 'Before delimiter'], + ['DELIMITER' => []], + ['MESSAGE' => 'After delimiter'], + ], + message: 'DELIMITER block default', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts DELIMITER block with size only')] + public function testAddAttachDelimiterBlockWithSize(): void + { + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => 'Before delimiter'], + ['DELIMITER' => [ + 'SIZE' => 200, + ]], + ['MESSAGE' => 'After delimiter'], + ], + message: 'DELIMITER block size', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts DELIMITER block with size and color')] + public function testAddAttachDelimiterBlockWithSizeAndColor(): void + { + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => 'Before delimiter'], + ['DELIMITER' => [ + 'SIZE' => 200, + 'COLOR' => '#c6c6c6', + ]], + ['MESSAGE' => 'After delimiter'], + ], + message: 'DELIMITER block color', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachFileBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachFileBlockTest.php new file mode 100644 index 00000000..21848003 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachFileBlockTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachFileBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts FILE block with minimal link only')] + public function testAddAttachFileBlockMinimal(): void + { + $messageId = $this->sendAttach( + attach: [ + ['FILE' => [ + [ + 'LINK' => self::DOCS_FILE_URL, + ], + ]], + ], + message: 'FILE block minimal', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts FILE block with name and size')] + public function testAddAttachFileBlockWithNameAndSize(): void + { + $messageId = $this->sendAttach( + attach: [ + ['FILE' => [ + [ + 'NAME' => 'attach-preview.png', + 'LINK' => self::DOCS_FILE_URL, + 'SIZE' => 1500000, + ], + ]], + ], + message: 'FILE block full', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts FILE block with multiple files')] + public function testAddAttachFileBlockMultipleFiles(): void + { + $messageId = $this->sendAttach( + attach: [ + ['FILE' => [ + [ + 'NAME' => 'attach-preview-1.png', + 'LINK' => self::DOCS_FILE_URL, + 'SIZE' => 1500000, + ], + [ + 'NAME' => 'attach-preview-2.png', + 'LINK' => self::DOCS_FILE_URL, + 'SIZE' => 1600000, + ], + ]], + ], + message: 'FILE block multiple', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachFormatTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachFormatTest.php new file mode 100644 index 00000000..a1c5cf79 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachFormatTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachFormatTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts ATTACH full form with metadata and blocks')] + public function testAddMessageWithFullAttachForm(): void + { + $messageId = $this->sendAttach( + attach: $this->createFullAttach( + blocks: [ + ['MESSAGE' => 'Full attach form payload'], + ], + id: 1, + colorToken: 'primary', + color: '#29619b', + ), + message: 'Full attach form', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts ATTACH short form without metadata wrapper')] + public function testAddMessageWithShortAttachForm(): void + { + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => 'Short attach form payload'], + ], + message: 'Short attach form', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachGridBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachGridBlockTest.php new file mode 100644 index 00000000..5448f391 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachGridBlockTest.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachGridBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts GRID block with BLOCK display')] + public function testAddAttachGridBlockDisplayBlock(): void + { + $messageId = $this->sendAttach( + attach: [ + ['GRID' => [ + [ + 'NAME' => 'Description', + 'VALUE' => 'Structured entities in messenger messages', + 'DISPLAY' => 'BLOCK', + 'WIDTH' => 250, + ], + [ + 'NAME' => 'Category', + 'VALUE' => 'Wish', + 'DISPLAY' => 'BLOCK', + 'WIDTH' => 100, + ], + ]], + ], + message: 'GRID block BLOCK', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts GRID block with LINE display and color settings')] + public function testAddAttachGridBlockDisplayLine(): void + { + $messageId = $this->sendAttach( + attach: [ + ['GRID' => [ + [ + 'NAME' => 'Priority', + 'VALUE' => 'High', + 'COLOR_TOKEN' => 'alert', + 'COLOR' => '#ff0000', + 'DISPLAY' => 'LINE', + 'WIDTH' => 250, + ], + [ + 'NAME' => 'Category', + 'VALUE' => 'Wish', + 'DISPLAY' => 'LINE', + ], + ]], + ], + message: 'GRID block LINE', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts GRID block with ROW display')] + public function testAddAttachGridBlockDisplayRow(): void + { + $messageId = $this->sendAttach( + attach: [ + ['GRID' => [ + [ + 'NAME' => 'Priority', + 'VALUE' => 'High', + 'DISPLAY' => 'ROW', + 'WIDTH' => 250, + ], + [ + 'NAME' => 'Category', + 'VALUE' => 'Wish', + 'DISPLAY' => 'ROW', + ], + ]], + ], + message: 'GRID block ROW', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts GRID block with TABLE display')] + public function testAddAttachGridBlockDisplayTable(): void + { + $messageId = $this->sendAttach( + attach: [ + ['GRID' => [ + [ + 'NAME' => 'Project', + 'VALUE' => 'BUGS', + 'DISPLAY' => 'TABLE', + ], + [ + 'NAME' => 'Category', + 'VALUE' => 'im', + 'DISPLAY' => 'TABLE', + ], + [ + 'NAME' => 'Deadline', + 'VALUE' => '04.11.2015 17:50:43', + 'DISPLAY' => 'TABLE', + ], + ]], + ], + message: 'GRID block TABLE', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts GRID block with BBCode values and entity links')] + public function testAddAttachGridBlockWithBbCodeAndEntityLinks(): void + { + $chatId = $this->createChat(); + + $messageId = $this->sendAttach( + attach: [ + ['GRID' => [ + [ + 'NAME' => 'Owner', + 'VALUE' => sprintf('[USER=%d][B]Current user[/B][/USER]', $this->currentUserId), + 'DISPLAY' => 'ROW', + 'USER_ID' => $this->currentUserId, + ], + [ + 'NAME' => 'Chat', + 'VALUE' => sprintf('[CHAT=%d]Payload chat[/CHAT]', $chatId), + 'DISPLAY' => 'ROW', + 'CHAT_ID' => $chatId, + ], + [ + 'NAME' => 'Action', + 'VALUE' => '[PUT=/help]Insert help[/PUT][BR][URL=https://bitrix24.ru]Portal[/URL]', + 'DISPLAY' => 'ROW', + 'LINK' => self::DOCS_LINK_URL, + 'HEIGHT' => 80, + ], + ]], + ], + message: 'GRID block BBCode', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachImageBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachImageBlockTest.php new file mode 100644 index 00000000..29ca320a --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachImageBlockTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachImageBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts IMAGE block with a single minimal image item')] + public function testAddAttachImageBlockMinimal(): void + { + $messageId = $this->sendAttach( + attach: [ + ['IMAGE' => [ + [ + 'LINK' => self::DOCS_IMAGE_URL, + ], + ]], + ], + message: 'IMAGE block minimal', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts IMAGE block with preview name and dimensions')] + public function testAddAttachImageBlockFullVariant(): void + { + $messageId = $this->sendAttach( + attach: [ + ['IMAGE' => [ + [ + 'NAME' => 'This is attach preview', + 'LINK' => self::DOCS_IMAGE_URL, + 'PREVIEW' => self::DOCS_IMAGE_URL, + 'WIDTH' => 1000, + 'HEIGHT' => 638, + ], + ]], + ], + message: 'IMAGE block full', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts IMAGE block with multiple images')] + public function testAddAttachImageBlockMultipleImages(): void + { + $messageId = $this->sendAttach( + attach: [ + ['IMAGE' => [ + [ + 'NAME' => 'First image', + 'LINK' => self::DOCS_IMAGE_URL, + ], + [ + 'NAME' => 'Second image', + 'LINK' => self::DOCS_IMAGE_URL, + 'PREVIEW' => self::DOCS_IMAGE_URL, + 'WIDTH' => 600, + 'HEIGHT' => 400, + ], + ]], + ], + message: 'IMAGE block multiple', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachLinkBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachLinkBlockTest.php new file mode 100644 index 00000000..2765bcaa --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachLinkBlockTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachLinkBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts LINK block with minimal external link payload')] + public function testAddAttachLinkBlockMinimal(): void + { + $messageId = $this->sendAttach( + attach: [ + ['LINK' => [ + 'LINK' => self::DOCS_LINK_URL, + ]], + ], + message: 'LINK block minimal', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts LINK block with preview name description and dimensions')] + public function testAddAttachLinkBlockWithDescriptionAndPreview(): void + { + $messageId = $this->sendAttach( + attach: [ + ['LINK' => [ + 'PREVIEW' => self::DOCS_IMAGE_URL, + 'WIDTH' => 1000, + 'HEIGHT' => 638, + 'NAME' => 'Ticket #12345: new messenger API', + 'DESC' => 'Implement before release', + 'LINK' => self::DOCS_LINK_URL, + ]], + ], + message: 'LINK block with preview', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts LINK block with HTML description')] + public function testAddAttachLinkBlockWithHtmlDescription(): void + { + $messageId = $this->sendAttach( + attach: [ + ['LINK' => [ + 'NAME' => 'HTML link block', + 'HTML' => 'Rich description for attach link', + 'LINK' => self::DOCS_LINK_URL, + ]], + ], + message: 'LINK block with HTML', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts LINK block navigation variants via user chat and network identifiers')] + public function testAddAttachLinkBlockEntityVariants(): void + { + $chatId = $this->createChat(); + + $messageId = $this->sendAttach( + attach: [ + ['LINK' => [ + 'NAME' => 'Current user', + 'USER_ID' => $this->currentUserId, + ]], + ['LINK' => [ + 'NAME' => 'Current chat', + 'CHAT_ID' => $chatId, + ]], + ['LINK' => [ + 'NAME' => 'Network user', + 'NETWORK_ID' => 'network-user-example', + ]], + ], + message: 'LINK block entity variants', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachMessageBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachMessageBlockTest.php new file mode 100644 index 00000000..20fe749d --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachMessageBlockTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachMessageBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts MESSAGE block with plain text')] + public function testAddAttachMessageBlockPlainText(): void + { + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => 'API will be available in update im 24.0.0'], + ], + message: 'MESSAGE block plain text', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts MESSAGE block with all documented BBCode variants')] + public function testAddAttachMessageBlockWithAllBbCodes(): void + { + $chatId = $this->createChat(); + + $messageId = $this->sendAttach( + attach: [ + ['MESSAGE' => implode('[BR]', [ + sprintf('[USER=%d]Current user[/USER]', $this->currentUserId), + sprintf('[CHAT=%d]Payload chat[/CHAT]', $chatId), + '[SEND=/help]Send help[/SEND]', + '[PUT=/search]Put search[/PUT]', + '[CALL=+79991234567]Call us[/CALL]', + '[B]Bold[/B] [U]Underline[/U] [I]Italic[/I] [S]Strike[/S]', + '[URL=https://bitrix24.ru]Bitrix24[/URL]', + ])], + ], + message: 'MESSAGE block BBCode', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachObjectApiTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachObjectApiTest.php new file mode 100644 index 00000000..363ab836 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachObjectApiTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please see the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Attach\Attach; +use Bitrix24\SDK\Services\IM\Message\Attach\Enums\AttachColorToken; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachObjectApiTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts ATTACH object short form')] + public function testAddAcceptsAttachObjectShortForm(): void + { + $messageId = $this->sendMessage( + message: 'Attach object short form', + attach: Attach::create()->message('Short object payload'), + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts ATTACH object full form')] + public function testAddAcceptsAttachObjectFullForm(): void + { + $messageId = $this->sendMessage( + message: 'Attach object full form', + attach: Attach::create() + ->id(1) + ->colorToken(AttachColorToken::primary) + ->message('Full object payload'), + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageAttachUserBlockTest.php b/tests/Integration/Services/IM/Message/Service/MessageAttachUserBlockTest.php new file mode 100644 index 00000000..a4695416 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageAttachUserBlockTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageAttachUserBlockTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts USER block with minimum required name')] + public function testAddAttachUserBlockMinimal(): void + { + $messageId = $this->sendAttach( + attach: [ + ['USER' => [ + 'NAME' => 'Ivan Ivanov', + ]], + ], + message: 'USER block minimal', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts USER block with avatar and external link')] + public function testAddAttachUserBlockWithAvatarAndLink(): void + { + $messageId = $this->sendAttach( + attach: [ + ['USER' => [ + 'NAME' => 'Ivan Ivanov', + 'AVATAR' => $this->currentUserPhotoUrl !== '' ? $this->currentUserPhotoUrl : self::DOCS_IMAGE_URL, + 'LINK' => self::DOCS_LINK_URL, + ]], + ], + message: 'USER block with avatar and link', + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts USER block navigation variants via user chat bot and network identifiers')] + public function testAddAttachUserBlockEntityVariants(): void + { + $chatId = $this->createChat(); + + $messageId = $this->sendAttach( + attach: [ + ['USER' => [ + 'NAME' => 'Portal user', + 'USER_ID' => $this->currentUserId, + 'AVATAR_TYPE' => 'USER', + ]], + ['USER' => [ + 'NAME' => 'Payload chat', + 'CHAT_ID' => $chatId, + 'AVATAR_TYPE' => 'CHAT', + ]], + ['USER' => [ + 'NAME' => 'Payload bot', + 'BOT_ID' => 1, + 'AVATAR_TYPE' => 'BOT', + ]], + ['USER' => [ + 'NAME' => 'Network user', + 'NETWORK_ID' => 'network-user-example', + ]], + ], + message: 'USER block entity variants', + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageChatPayloadTest.php b/tests/Integration/Services/IM/Message/Service/MessageChatPayloadTest.php new file mode 100644 index 00000000..5cd1c98f --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageChatPayloadTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageChatPayloadTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add sends a plain user message to a group chat')] + public function testAddPlainMessageToChat(): void + { + $messageId = $this->sendMessage( + message: sprintf('Plain payload test at %s', uniqid('', true)), + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php b/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php new file mode 100644 index 00000000..1633ac41 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Chat\ChatType; +use Bitrix24\SDK\Services\IM\Chat\Service\Chat; +use Bitrix24\SDK\Services\IM\Message\Attach\Contracts\AttachPayloadInterface; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\TestCase; + +abstract class MessageChatTestCase extends TestCase +{ + protected const DOCS_IMAGE_URL = 'https://apidocs.bitrix24.ru/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/attachments/_images/attach1.png'; + + protected const DOCS_LINK_URL = 'https://apidocs.bitrix24.ru'; + + protected const DOCS_FILE_URL = 'https://apidocs.bitrix24.ru/api-reference/chat-bots/chat-bots-v2/imbot.v2/messages/attachments/_images/attach1.png'; + + protected Message $messageService; + + protected Chat $chatService; + + protected int $currentUserId; + + protected string $currentUserPhotoUrl = ''; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $imServiceBuilder = Factory::getServiceBuilder()->getIMScope(); + $this->messageService = $imServiceBuilder->message(); + $this->chatService = $imServiceBuilder->chat(); + $profile = $this->messageService->core->call('PROFILE') + ->getResponseData()->getResult(); + $this->currentUserId = (int)$profile['ID']; + $this->currentUserPhotoUrl = (string)($profile['PERSONAL_PHOTO'] ?? ''); + } + + /** + * @throws BaseException + */ + #[\Override] + protected function tearDown(): void + { + // Cleanup is intentionally disabled so message/chat payloads remain visible in the portal + // for manual inspection after the integration test run. + // foreach ($createdChats as $chatId) { + // try { + // $this->chatService->leave($chatId); + // } catch (BaseException) { + // // Chat may already be left by the portal or another cleanup path. + // } + // } + } + + /** + * @throws BaseException + * @throws TransportException + */ + protected function sendMessage( + ?string $message = null, + array|string|AttachPayloadInterface|null $attach = null, + array|string|null $keyboard = null, + array|string|null $menu = null, + ): int { + return $this->messageService->add( + dialogId: $this->createDialogId(), + message: $message, + attach: $attach, + keyboard: $keyboard, + menu: $menu, + )->getId(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + protected function sendAttach(array|string|AttachPayloadInterface $attach, ?string $message = null): int + { + return $this->sendMessage( + message: $message, + attach: $attach, + ); + } + + /** + * @param list> $blocks + * + * @return array + */ + protected function createFullAttach( + array $blocks, + int $id = 1, + ?string $colorToken = null, + ?string $color = null, + ): array { + $attach = [ + 'ID' => $id, + 'BLOCKS' => $blocks, + ]; + + if ($colorToken !== null) { + $attach['COLOR_TOKEN'] = $colorToken; + } + + if ($color !== null) { + $attach['COLOR'] = $color; + } + + return $attach; + } + + /** + * @throws BaseException + * @throws TransportException + */ + protected function createDialogId(): string + { + return $this->getDialogId($this->createChat()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + protected function createChat(): int + { + return $this->chatService->add( + users: [$this->currentUserId], + chatType: ChatType::Closed, + title: sprintf('Message payload %s', uniqid('', true)), + )->getId(); + } + + protected function getDialogId(int $chatId): string + { + return sprintf('chat%d', $chatId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageFormattingTest.php b/tests/Integration/Services/IM/Message/Service/MessageFormattingTest.php new file mode 100644 index 00000000..a7997087 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageFormattingTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageFormattingTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts basic BBCode formatting payloads')] + public function testAddMessageWithBasicFormatting(): void + { + $messageId = $this->sendMessage( + message: implode('[br]', [ + '[b]Bold[/b] [i]Italic[/i]', + '[u]Underline[/u] [s]Strike[/s]', + '[color=#ff0000]Red[/color] [size=20]Large[/size]', + ]), + ); + + $this->assertGreaterThan(0, $messageId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts advanced BBCode formatting payloads')] + public function testAddMessageWithAdvancedFormatting(): void + { + $chatId = $this->createChat(); + $dialogId = $this->getDialogId($chatId); + + $messageId = $this->messageService->add( + dialogId: $dialogId, + message: implode('[br]', [ + sprintf('[user=%d]Current user[/user] joined [chat=%d]payload chat[/chat]', $this->currentUserId, $chatId), + '[url=https://bitrix24.ru]Bitrix24[/url]', + '>>quoted line', + '[code]payload::advanced();[/code]', + sprintf('[timestamp=%d format=SHORT_TIME_FORMAT]', time()), + ]), + )->getId(); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageKeyboardTest.php b/tests/Integration/Services/IM/Message/Service/MessageKeyboardTest.php new file mode 100644 index 00000000..144cd878 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageKeyboardTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageKeyboardTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts keyboard payloads with link newline and action buttons')] + public function testAddMessageWithKeyboard(): void + { + $messageId = $this->sendMessage( + message: 'Choose an action on keyboard', + keyboard: [ + 'BUTTONS' => [ + [ + 'TEXT' => 'Open site', + 'LINK' => 'https://www.example.ru/', + ], + [ + 'TYPE' => 'NEWLINE', + ], + [ + 'TEXT' => 'Put help', + 'ACTION' => 'PUT', + 'ACTION_VALUE' => '/help', + ], + ], + ], + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageMenuTest.php b/tests/Integration/Services/IM/Message/Service/MessageMenuTest.php new file mode 100644 index 00000000..171fb4c2 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageMenuTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; + +#[CoversClass(Message::class)] +class MessageMenuTest extends MessageChatTestCase +{ + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add accepts menu payloads with link and action items')] + public function testAddMessageWithMenu(): void + { + $messageId = $this->sendMessage( + message: 'Choose an action in menu', + menu: [ + 'ITEMS' => [ + [ + 'TEXT' => 'Open site', + 'LINK' => 'https://www.example.ru/', + ], + [ + 'TEXT' => 'Send text', + 'ACTION' => 'SEND', + 'ACTION_VALUE' => 'Ready', + ], + ], + ], + ); + + $this->assertGreaterThan(0, $messageId); + } +} diff --git a/tests/Integration/Services/IM/Message/Service/MessageTest.php b/tests/Integration/Services/IM/Message/Service/MessageTest.php new file mode 100644 index 00000000..d6247c39 --- /dev/null +++ b/tests/Integration/Services/IM/Message/Service/MessageTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Message\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Message\Service\LikeAction; +use Bitrix24\SDK\Services\IM\Message\Service\Message; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Message::class)] +class MessageTest extends TestCase +{ + private Message $service; + + private int $currentUserId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $this->service = Factory::getServiceBuilder()->getIMScope()->message(); + $this->currentUserId = (int)$this->service->core->call('PROFILE') + ->getResponseData()->getResult()['ID']; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('add sends a personal message and returns its ID')] + public function testAdd(): void + { + $addedItemResult = $this->service->add( + dialogId: (string)$this->currentUserId, + message: sprintf('Test add at %s', time()), + ); + $this->assertGreaterThan(0, $addedItemResult->getId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('update edits a previously sent message')] + public function testUpdate(): void + { + $messageId = $this->service->add( + (string)$this->currentUserId, + sprintf('Before update at %s', time()), + )->getId(); + $this->assertTrue( + $this->service->update($messageId, 'Updated text')->isSuccess() + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('delete removes a previously sent message')] + public function testDelete(): void + { + $messageId = $this->service->add( + (string)$this->currentUserId, + sprintf('To delete at %s', time()), + )->getId(); + $this->assertTrue($this->service->delete($messageId)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('like toggles the Like mark on a message')] + public function testLike(): void + { + $messageId = $this->service->add( + (string)$this->currentUserId, + sprintf('To like at %s', time()), + )->getId(); + $this->assertTrue( + $this->service->like($messageId, LikeAction::plus)->isSuccess() + ); + } + + #[Test] + #[TestDox('command executes a chat-bot command')] + public function testCommand(): void + { + $this->markTestSkipped( + 'im.message.command requires a registered chat bot with a command; ' + . 'skipped in standard integration suite. ' + . 'Re-enable when a bot fixture is available.' + ); + } + + #[Test] + #[TestDox('share creates an object based on a message')] + public function testShare(): void + { + $this->markTestSkipped( + 'im.message.share requires a chat ID (not personal dialog); ' + . 'skipped in standard integration suite. ' + . 'Re-enable when a chat fixture is available.' + ); + } +} diff --git a/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php new file mode 100644 index 00000000..2f502e08 --- /dev/null +++ b/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifyItemResult; +use Bitrix24\SDK\Services\IM\Notify\Service\Notify; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(NotifyItemResult::class)] +class NotifyItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Notify $notifyService; + + private int $currentUserId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in NotifyItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $this->notifyService->fromSystem($this->currentUserId, sprintf('Annotation test at %s', time())); + + $rawResult = $this->notifyService->getList(null, null, 1) + ->getCoreResponse()->getResponseData()->getResult(); + + $notifications = $rawResult['notifications'] ?? []; + $this->assertNotEmpty($notifications, 'No notifications returned — cannot verify annotations'); + + $rawItem = $notifications[0]; + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItem), + NotifyItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in NotifyItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeCastingInMagicGetters(): void + { + $this->notifyService->fromSystem($this->currentUserId, sprintf('Type cast test at %s', time())); + + $items = $this->notifyService->getList(null, null, 1)->notifications(); + $this->assertNotEmpty($items, 'No notifications returned — cannot verify type casting'); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + NotifyItemResult::class + ); + } + + #[\Override] + protected function setUp(): void + { + $this->notifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->currentUserId = (int)$this->notifyService->core->call('PROFILE') + ->getResponseData()->getResult()['ID']; + } +} diff --git a/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php new file mode 100644 index 00000000..857d5a49 --- /dev/null +++ b/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Notify\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Notify\Result\NotifySchemaItemResult; +use Bitrix24\SDK\Services\IM\Notify\Service\Notify; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(NotifySchemaItemResult::class)] +class NotifySchemaItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Notify $notifyService; + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in NotifySchemaItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->notifyService->getSchema() + ->getCoreResponse()->getResponseData()->getResult(); + + $modules = array_filter($rawResult, 'is_array'); + $this->assertNotEmpty($modules, 'Schema returned no modules — cannot verify annotations'); + + $rawItem = reset($modules); + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItem), + NotifySchemaItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in NotifySchemaItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeCastingInMagicGetters(): void + { + $items = $this->notifyService->getSchema()->schema(); + $this->assertNotEmpty($items, 'Schema returned no modules — cannot verify type casting'); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + NotifySchemaItemResult::class + ); + } + + #[\Override] + protected function setUp(): void + { + $this->notifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + } +} diff --git a/tests/Integration/Services/IM/Notify/Service/NotifyTest.php b/tests/Integration/Services/IM/Notify/Service/NotifyTest.php new file mode 100644 index 00000000..f4cadadf --- /dev/null +++ b/tests/Integration/Services/IM/Notify/Service/NotifyTest.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Notify\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Notify\Service\Notify; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Notify::class)] +class NotifyTest extends TestCase +{ + private Notify $imNotifyService; + + private int $currentUserId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test send notification from system')] + public function testFromSystem(): void + { + $addedItemResult = $this->imNotifyService->fromSystem($this->currentUserId, sprintf('Test message at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test send notification from personal')] + public function testFromPersonal(): void + { + $addedItemResult = $this->imNotifyService->fromPersonal($this->currentUserId, sprintf('Test message at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test send notification via im.notify')] + public function testSend(): void + { + $addedItemResult = $this->imNotifyService->send( + $this->currentUserId, + sprintf('Test notify at %s', time()), + 'USER' + ); + $this->assertGreaterThan(0, $addedItemResult->getId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test delete notification')] + public function testDelete(): void + { + $addedItemResult = $this->imNotifyService->fromSystem($this->currentUserId, sprintf('Test message for delete at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + $this->assertTrue($this->imNotifyService->delete($addedItemResult->getId())->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test mark as read notification')] + public function testMarkAsRead(): void + { + $addedItemResult = $this->imNotifyService->fromSystem($this->currentUserId, sprintf('Test message for mark as read at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + $this->assertTrue($this->imNotifyService->markAsRead($addedItemResult->getId())->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test mark messages as read via im.notify.read.list')] + public function testMarkMessagesAsRead(): void + { + $ids = []; + for ($i = 0; $i < 3; $i++) { + $ids[] = $this->imNotifyService->fromSystem( + $this->currentUserId, + sprintf('Test message for bulk read at %s', time()) + )->getId(); + } + + $this->assertTrue($this->imNotifyService->markMessagesAsRead($ids)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test mark messages as unread via im.notify.read.list')] + public function testMarkMessagesAsUnread(): void + { + $ids = []; + for ($i = 0; $i < 3; $i++) { + $ids[] = $this->imNotifyService->fromSystem( + $this->currentUserId, + sprintf('Test message for bulk unread at %s', time()) + )->getId(); + } + + $this->assertTrue($this->imNotifyService->markMessagesAsRead($ids)->isSuccess()); + $this->assertTrue($this->imNotifyService->markMessagesAsUnread($ids)->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test get list of notifications')] + public function testGetList(): void + { + $this->imNotifyService->fromSystem($this->currentUserId, sprintf('Test for getList at %s', time())); + + $notifiesResult = $this->imNotifyService->getList(null, null, 10); + $this->assertIsArray($notifiesResult->notifications()); + $this->assertGreaterThanOrEqual(0, $notifiesResult->totalCount()); + $this->assertGreaterThanOrEqual(0, $notifiesResult->totalUnreadCount()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test mark all notifications as read')] + public function testMarkAllAsRead(): void + { + $this->imNotifyService->fromSystem($this->currentUserId, sprintf('Test for markAllAsRead at %s', time())); + + $notifyReadAllResult = $this->imNotifyService->markAllAsRead(); + $this->assertTrue($notifyReadAllResult->isSuccess()); + $this->assertGreaterThanOrEqual(0, $notifyReadAllResult->newCounter()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test search notification history')] + public function testHistorySearch(): void + { + $notifyHistorySearchResult = $this->imNotifyService->historySearch(searchText: 'test', limit: 10); + $this->assertIsArray($notifyHistorySearchResult->notifications()); + $this->assertGreaterThanOrEqual(0, $notifyHistorySearchResult->totalResults()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test get notification schema')] + public function testGetSchema(): void + { + $notifySchemaResult = $this->imNotifyService->getSchema(); + $this->assertNotEmpty($notifySchemaResult->schema()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test Interaction with notification buttons')] + public function testConfirm(): void + { + $addedItemResult = $this->imNotifyService->fromPersonal($this->currentUserId, sprintf('Test message at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + $this->imNotifyService->confirm($addedItemResult->getId(), true); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('Test response to notification with quick reply')] + public function testAnswer(): void + { + $addedItemResult = $this->imNotifyService->fromPersonal($this->currentUserId, sprintf('Test message at %s', time())); + $this->assertGreaterThan(0, $addedItemResult->getId()); + $this->imNotifyService->answer($addedItemResult->getId(), 'reply text'); + } + + #[\Override] + protected function setUp(): void + { + $this->imNotifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->currentUserId = (int)$this->imNotifyService->core->call('PROFILE') + ->getResponseData()->getResult()['ID']; + } +} diff --git a/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php b/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php new file mode 100644 index 00000000..d54f18f1 --- /dev/null +++ b/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Placements; + +use Bitrix24\SDK\Services\IM\Placements\ContextMenuPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\PlacementColor; +use Bitrix24\SDK\Services\IM\Placements\PlacementLocationCodes; +use Bitrix24\SDK\Services\IM\Placements\SidebarPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\TextareaPlacementOptions; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; +use ReflectionClass; + +#[CoversClass(PlacementLocationCodes::class)] +class PlacementLocationCodesTest extends TestCase +{ + private ServiceBuilder $sb; + + #[Test] + #[TestDox('PlacementLocationCodes class declares every IM placement code returned by placement.list')] + public function testAllApiImPlacementsAreDeclared(): void + { + $remoteCodes = $this->sb->getPlacementScope()->placement()->list()->getLocationCodes(); + + $remoteImCodes = array_values(array_filter( + $remoteCodes, + static fn (string $code): bool => str_starts_with($code, 'IM_'), + )); + + $reflectionClass = new ReflectionClass(PlacementLocationCodes::class); + $declaredCodes = array_values($reflectionClass->getConstants()); + + $missing = array_values(array_diff($remoteImCodes, $declaredCodes)); + + $this->assertSame( + [], + $missing, + sprintf( + 'PlacementLocationCodes is missing constants for IM placements returned by placement.list: %s', + implode(', ', $missing), + ), + ); + } + + #[\Override] + protected function setUp(): void + { + $this->sb = Factory::getServiceBuilder(true); + } +} diff --git a/tests/Integration/Services/IM/Placements/PlacementsTest.php b/tests/Integration/Services/IM/Placements/PlacementsTest.php new file mode 100644 index 00000000..c6a05550 --- /dev/null +++ b/tests/Integration/Services/IM/Placements/PlacementsTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Placements; + +use Bitrix24\SDK\Core\Contracts\LangCodes; +use Bitrix24\SDK\Services\IM\Placements\ImContextMenuPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\ImNavigationPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\ImSidebarPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\ImTextareaPlacementOptions; +use Bitrix24\SDK\Services\IM\Placements\PlacementColor; +use Bitrix24\SDK\Services\IM\Placements\PlacementLangItem; +use Bitrix24\SDK\Services\IM\Placements\PlacementLangMap; +use Bitrix24\SDK\Services\IM\Placements\Placements; +use Bitrix24\SDK\Services\ServiceBuilder; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Placements::class)] +class PlacementsTest extends TestCase +{ + private ServiceBuilder $sb; + + #[Test] + public function testTypedBindAndUnbindMethods(): void + { + $placements = $this->sb->getIMScope()->placements(); + + $placements->unbindSidebar(); + $placements->unbindNavigation(); + $placements->unbindContextMenu(); + $placements->unbindTextarea(); + $placements->unbindSmilesSelector(); + + $placementLangMap = PlacementLangMap::empty() + ->with(LangCodes::EN, new PlacementLangItem('Sidebar')) + ->with(LangCodes::RU, new PlacementLangItem('Сайдбар')); + + self::assertTrue( + $placements->bindSidebar( + 'https://bitrix24test.com/im-sidebar', + $placementLangMap, + (new ImSidebarPlacementOptions('fa-bug')) + ->color(PlacementColor::Green), + )->isSuccess() + ); + + self::assertTrue( + $placements->bindNavigation( + 'https://bitrix24test.com/im-navigation', + PlacementLangMap::empty() + ->with(LangCodes::EN, new PlacementLangItem('Navigation')), + new ImNavigationPlacementOptions('fa-compass'), + )->isSuccess() + ); + + self::assertTrue( + $placements->bindContextMenu( + 'https://bitrix24test.com/im-context-menu', + PlacementLangMap::empty() + ->with(LangCodes::EN, new PlacementLangItem('Context menu')), + new ImContextMenuPlacementOptions(), + )->isSuccess() + ); + + self::assertTrue( + $placements->bindTextarea( + 'https://bitrix24test.com/im-textarea', + PlacementLangMap::empty() + ->with(LangCodes::EN, new PlacementLangItem('Textarea')), + (new ImTextareaPlacementOptions('fa-comment')) + ->width(400) + ->height(160) + ->color(PlacementColor::Brown), + )->isSuccess() + ); + + self::assertTrue( + $placements->bindSmilesSelector( + 'https://bitrix24test.com/im-smiles-selector', + PlacementLangMap::empty() + ->with(LangCodes::EN, new PlacementLangItem('Smiles selector')), + ['name' => 'fa-face-smile'], + )->isSuccess() + ); + + self::assertGreaterThanOrEqual(0, $placements->unbindSidebar()->getDeletedPlacementHandlersCount()); + self::assertGreaterThanOrEqual(0, $placements->unbindNavigation()->getDeletedPlacementHandlersCount()); + self::assertGreaterThanOrEqual(0, $placements->unbindContextMenu()->getDeletedPlacementHandlersCount()); + self::assertGreaterThanOrEqual(0, $placements->unbindTextarea()->getDeletedPlacementHandlersCount()); + self::assertGreaterThanOrEqual(0, $placements->unbindSmilesSelector()->getDeletedPlacementHandlersCount()); + } + + #[\Override] + protected function setUp(): void + { + $this->sb = Factory::getServiceBuilder(true); + } +} diff --git a/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php new file mode 100644 index 00000000..43ecb13b --- /dev/null +++ b/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Recent\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Recent\Result\RecentItemResult; +use Bitrix24\SDK\Services\IM\Recent\Service\Recent; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RecentItemResult::class)] +class RecentItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Recent $recentService; + + #[\Override] + protected function setUp(): void + { + $this->recentService = Factory::getServiceBuilder()->getIMScope()->recent(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in RecentItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->recentService->get() + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + if ($rawResult === []) { + $this->markTestSkipped('No recent items available to validate annotations'); + } + + $firstItem = reset($rawResult); + if (!is_array($firstItem)) { + $this->markTestSkipped('Unexpected response shape for im.recent.get'); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($firstItem), + RecentItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in RecentItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $items = $this->recentService->get()->items(); + + if ($items === []) { + $this->markTestSkipped('No recent items available to validate type casting'); + } + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + RecentItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Recent/Service/RecentTest.php b/tests/Integration/Services/IM/Recent/Service/RecentTest.php new file mode 100644 index 00000000..776a5a6a --- /dev/null +++ b/tests/Integration/Services/IM/Recent/Service/RecentTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Recent\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Recent\Service\Recent; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Recent::class)] +class RecentTest extends TestCase +{ + private Recent $recentService; + + #[\Override] + protected function setUp(): void + { + $this->recentService = Factory::getServiceBuilder()->getIMScope()->recent(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.recent.get returns a list of RecentItemResult')] + public function testGet(): void + { + $items = $this->recentService->get()->items(); + + $this->assertIsArray($items); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.recent.list returns a paginated list of RecentItemResult')] + public function testList(): void + { + $items = $this->recentService->list(limit: 10)->items(); + + $this->assertIsArray($items); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.recent.pin pins and unpins the first dialog in the recent list')] + public function testPin(): void + { + $items = $this->recentService->get()->items(); + if ($items === []) { + $this->markTestSkipped('No recent dialogs available for pin test'); + } + + $dialogId = $items[0]->id; + + $updatedItemResult = $this->recentService->pin($dialogId, true); + $this->assertTrue($updatedItemResult->isSuccess()); + + $unpinResult = $this->recentService->pin($dialogId, false); + $this->assertTrue($unpinResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.recent.unread marks the first dialog as unread and then read')] + public function testUnread(): void + { + $items = $this->recentService->get()->items(); + if ($items === []) { + $this->markTestSkipped('No recent dialogs available for unread test'); + } + + $dialogId = $items[0]->id; + + $updatedItemResult = $this->recentService->unread($dialogId, 'mark'); + $this->assertTrue($updatedItemResult->isSuccess()); + + $unmarkResult = $this->recentService->unread($dialogId, 'unmark'); + $this->assertTrue($unmarkResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.recent.hide removes the first dialog from the recent list')] + public function testHide(): void + { + $items = $this->recentService->get()->items(); + if ($items === []) { + $this->markTestSkipped('No recent dialogs available for hide test'); + } + + $dialogId = $items[0]->id; + + $updatedItemResult = $this->recentService->hide($dialogId); + $this->assertTrue($updatedItemResult->isSuccess()); + } +} diff --git a/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php b/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php new file mode 100644 index 00000000..378e7595 --- /dev/null +++ b/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Revision\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Revision\Result\RevisionItemResult; +use Bitrix24\SDK\Services\IM\Revision\Service\Revision; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(RevisionItemResult::class)] +class RevisionItemResultTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Revision $revisionService; + + #[\Override] + protected function setUp(): void + { + $this->revisionService = Factory::getServiceBuilder()->getIMScope()->revision(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in RevisionItemResult are annotated in phpdoc and match with raw api response')] + public function testAllFieldsAreAnnotated(): void + { + $rawItem = $this->revisionService->get() + ->getCoreResponse()->getResponseData()->getResult(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawItem), + RevisionItemResult::class, + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in RevisionItemResult have valid type casting in magic getters')] + public function testAllFieldsHasValidTypeCastingInMagicGetters(): void + { + $revisionItemResult = $this->revisionService->get()->revision(); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $revisionItemResult, + RevisionItemResult::class, + ); + } +} diff --git a/tests/Integration/Services/IM/Revision/Service/RevisionTest.php b/tests/Integration/Services/IM/Revision/Service/RevisionTest.php new file mode 100644 index 00000000..69f3a1b2 --- /dev/null +++ b/tests/Integration/Services/IM/Revision/Service/RevisionTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Revision\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Revision\Service\Revision; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Revision::class)] +class RevisionTest extends TestCase +{ + private Revision $revisionService; + + #[\Override] + protected function setUp(): void + { + $this->revisionService = Factory::getServiceBuilder()->getIMScope()->revision(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.revision.get returns non-negative revision numbers')] + public function testGet(): void + { + $revision = $this->revisionService->get()->revision(); + + $this->assertGreaterThanOrEqual(0, $revision->rest); + $this->assertGreaterThanOrEqual(0, $revision->mobile); + $this->assertGreaterThanOrEqual(0, $revision->web); + $this->assertGreaterThanOrEqual(0, $revision->desktop); + $this->assertGreaterThanOrEqual(0, $revision->im_revision_mobile); + } +} diff --git a/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php new file mode 100644 index 00000000..899d6091 --- /dev/null +++ b/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Search\Result\SearchChatItemResult; +use Bitrix24\SDK\Services\IM\Search\Service\Search; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SearchChatItemResult::class)] +class SearchChatItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Search $searchService; + + #[\Override] + protected function setUp(): void + { + $this->searchService = Factory::getServiceBuilder()->getIMScope()->search(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchChatItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->searchService->chatList(find: 'Test', limit: 1) + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + if ($rawResult === []) { + $this->markTestSkipped('No chat search results available to validate annotations'); + } + + $firstItem = reset($rawResult); + if (!is_array($firstItem)) { + $this->markTestSkipped('Unexpected response shape for im.search.chat.list'); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($firstItem), + SearchChatItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchChatItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $items = $this->searchService->chatList(find: 'Test', limit: 1)->items(); + + if ($items === []) { + $this->markTestSkipped('No chat search results available to validate type casting'); + } + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + SearchChatItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php new file mode 100644 index 00000000..4a3ec16d --- /dev/null +++ b/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Search\Result\SearchDepartmentItemResult; +use Bitrix24\SDK\Services\IM\Search\Service\Search; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SearchDepartmentItemResult::class)] +class SearchDepartmentItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Search $searchService; + + #[\Override] + protected function setUp(): void + { + $this->searchService = Factory::getServiceBuilder()->getIMScope()->search(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchDepartmentItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->searchService->departmentList(find: 'Отд', userData: true, limit: 1) + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + if ($rawResult === []) { + $this->markTestSkipped('No department search results available to validate annotations'); + } + + $firstItem = reset($rawResult); + if (!is_array($firstItem)) { + $this->markTestSkipped('Unexpected response shape for im.search.department.list'); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($firstItem), + SearchDepartmentItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchDepartmentItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $items = $this->searchService->departmentList(find: 'Отд', userData: true, limit: 1)->items(); + + if ($items === []) { + $this->markTestSkipped('No department search results available to validate type casting'); + } + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + SearchDepartmentItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php new file mode 100644 index 00000000..38815ad0 --- /dev/null +++ b/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Search\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Search\Result\SearchUserItemResult; +use Bitrix24\SDK\Services\IM\Search\Service\Search; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(SearchUserItemResult::class)] +class SearchUserItemResultAnnotationsTest extends TestCase +{ + use CustomBitrix24Assertions; + + private Search $searchService; + + #[\Override] + protected function setUp(): void + { + $this->searchService = Factory::getServiceBuilder()->getIMScope()->search(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchUserItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->searchService->userList(find: 'Maksim', limit: 1) + ->getCoreResponse() + ->getResponseData() + ->getResult(); + + if ($rawResult === []) { + $this->markTestSkipped('No user search results available to validate annotations'); + } + + $firstItem = reset($rawResult); + if (!is_array($firstItem)) { + $this->markTestSkipped('Unexpected response shape for im.search.user.list'); + } + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($firstItem), + SearchUserItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in SearchUserItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $items = $this->searchService->userList(find: 'Maksim', limit: 1)->items(); + + if ($items === []) { + $this->markTestSkipped('No user search results available to validate type casting'); + } + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $items[0], + SearchUserItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/Search/Service/SearchTest.php b/tests/Integration/Services/IM/Search/Service/SearchTest.php new file mode 100644 index 00000000..7bd121f3 --- /dev/null +++ b/tests/Integration/Services/IM/Search/Service/SearchTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\Search\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\Search\Result\SearchChatItemResult; +use Bitrix24\SDK\Services\IM\Search\Result\SearchDepartmentItemResult; +use Bitrix24\SDK\Services\IM\Search\Result\SearchUserItemResult; +use Bitrix24\SDK\Services\IM\Search\Service\Search; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Search::class)] +class SearchTest extends TestCase +{ + private Search $searchService; + + #[\Override] + protected function setUp(): void + { + $this->searchService = Factory::getServiceBuilder()->getIMScope()->search(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.search.chat.list returns a paginated list of SearchChatItemResult')] + public function testChatList(): void + { + $searchChatsResult = $this->searchService->chatList(find: 'Test', limit: 1); + $items = $searchChatsResult->items(); + + $this->assertIsArray($items); + $this->assertGreaterThanOrEqual(0, $searchChatsResult->total()); + $this->assertTrue($searchChatsResult->next() === null || $searchChatsResult->next() >= 0); + + if ($items === []) { + $this->markTestSkipped('No chat search results available for FIND=Test'); + } + + $this->assertInstanceOf(SearchChatItemResult::class, $items[0]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.search.user.list returns a list of SearchUserItemResult normalized from user-id keys')] + public function testUserList(): void + { + $searchUsersResult = $this->searchService->userList(find: 'Maksim', limit: 1); + $items = $searchUsersResult->items(); + + $this->assertIsArray($items); + $this->assertGreaterThanOrEqual(0, $searchUsersResult->total()); + + if ($items === []) { + $this->markTestSkipped('No user search results available for FIND=Maksim'); + } + + $this->assertInstanceOf(SearchUserItemResult::class, $items[0]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.search.department.list returns a paginated list of SearchDepartmentItemResult')] + public function testDepartmentList(): void + { + $searchDepartmentsResult = $this->searchService->departmentList(find: 'Отд', userData: true, limit: 3); + $items = $searchDepartmentsResult->items(); + + $this->assertIsArray($items); + $this->assertGreaterThanOrEqual(0, $searchDepartmentsResult->total()); + $this->assertTrue($searchDepartmentsResult->next() === null || $searchDepartmentsResult->next() >= 0); + + if ($items === []) { + $this->markTestSkipped('No department search results available for FIND=Отд'); + } + + $this->assertInstanceOf(SearchDepartmentItemResult::class, $items[0]); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('legacy im.search.last.add/get/delete endpoints manage last search history')] + public function testLastSearchCycle(): void + { + $dialogId = '1'; + + try { + $this->assertTrue($this->searchService->lastAdd($dialogId)->isSuccess()); + + $items = $this->searchService->lastGet()->items(); + $this->assertIsArray($items); + } finally { + $this->searchService->lastDelete($dialogId); + } + } +} diff --git a/tests/Integration/Services/IM/Service/NotifyTest.php b/tests/Integration/Services/IM/Service/NotifyTest.php index a90b42e3..0c70aaca 100644 --- a/tests/Integration/Services/IM/Service/NotifyTest.php +++ b/tests/Integration/Services/IM/Service/NotifyTest.php @@ -1,4 +1,4 @@ -imNotifyService->confirm($addedItemResult->getId(), true); } + #[\Override] protected function setUp(): void { - $this->imNotifyService = Fabric::getServiceBuilder()->getIMScope()->notify(); + $this->imNotifyService = Factory::getServiceBuilder()->getIMScope()->notify(); } } \ No newline at end of file diff --git a/tests/Integration/Services/IM/User/Result/UserItemResultTest.php b/tests/Integration/Services/IM/User/Result/UserItemResultTest.php new file mode 100644 index 00000000..0c49c330 --- /dev/null +++ b/tests/Integration/Services/IM/User/Result/UserItemResultTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\User\Result; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\User\Result\UserItemResult; +use Bitrix24\SDK\Services\IM\User\Service\User; +use Bitrix24\SDK\Tests\CustomAssertions\CustomBitrix24Assertions; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(UserItemResult::class)] +final class UserItemResultTest extends TestCase +{ + use CustomBitrix24Assertions; + + private User $userService; + + #[\Override] + protected function setUp(): void + { + $this->userService = Factory::getServiceBuilder()->getIMScope()->user(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in UserItemResult are annotated in phpdoc and match with raw api response')] + public function testAllSystemFieldsAnnotated(): void + { + $rawResult = $this->userService->get()->getCoreResponse() + ->getResponseData()->getResult(); + + $this->assertBitrix24AllResultItemFieldsAnnotated( + array_keys($rawResult), + UserItemResult::class + ); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('all fields in UserItemResult have valid type casting in magic getters')] + public function testAllSystemFieldsHasValidTypeAnnotation(): void + { + $userItemResult = $this->userService->get()->user(); + $this->assertInstanceOf(UserItemResult::class, $userItemResult); + + $this->assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + $userItemResult, + UserItemResult::class + ); + } +} diff --git a/tests/Integration/Services/IM/User/Service/UserStatusTest.php b/tests/Integration/Services/IM/User/Service/UserStatusTest.php new file mode 100644 index 00000000..93b5b376 --- /dev/null +++ b/tests/Integration/Services/IM/User/Service/UserStatusTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\User\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\User\Result\UserStatusItemResult; +use Bitrix24\SDK\Services\IM\User\Service\UserStatus; +use Bitrix24\SDK\Services\IM\User\UserStatusType; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(UserStatus::class)] +class UserStatusTest extends TestCase +{ + private UserStatus $userStatusService; + + #[\Override] + protected function setUp(): void + { + $this->userStatusService = Factory::getServiceBuilder()->getIMScope()->userStatus(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.status.get returns a status item result')] + public function testGet(): void + { + $userStatusItemResult = $this->userStatusService->get()->status(); + + $this->assertInstanceOf(UserStatusItemResult::class, $userStatusItemResult); + $this->assertTrue($userStatusItemResult->STATUS === null || $userStatusItemResult->STATUS instanceof UserStatusType); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.status.set returns success for UserStatusType::Online')] + public function testSet(): void + { + $updatedItemResult = $this->userStatusService->set(UserStatusType::Online); + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.status.idle.start returns success')] + public function testIdleStart(): void + { + $updatedItemResult = $this->userStatusService->idleStart(); + $this->assertTrue($updatedItemResult->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.status.idle.end returns success')] + public function testIdleEnd(): void + { + $updatedItemResult = $this->userStatusService->idleEnd(); + $this->assertTrue($updatedItemResult->isSuccess()); + } +} diff --git a/tests/Integration/Services/IM/User/Service/UserTest.php b/tests/Integration/Services/IM/User/Service/UserTest.php new file mode 100644 index 00000000..bd1ec7fa --- /dev/null +++ b/tests/Integration/Services/IM/User/Service/UserTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IM\User\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IM\User\Result\UserItemResult; +use Bitrix24\SDK\Services\IM\User\Service\User; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(User::class)] +final class UserTest extends TestCase +{ + private User $userService; + + #[\Override] + protected function setUp(): void + { + $this->userService = Factory::getServiceBuilder()->getIMScope()->user(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.get without ID returns the current user profile')] + public function testGetCurrentUser(): void + { + $user = $this->userService->get()->user(); + + $this->assertInstanceOf(UserItemResult::class, $user); + $this->assertGreaterThan(0, $user->id); + $this->assertNotEmpty($user->name); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.get with ID returns the specified user profile')] + public function testGetUserById(): void + { + $currentUser = $this->userService->get()->user(); + $this->assertInstanceOf(UserItemResult::class, $currentUser); + + $user = $this->userService->get($currentUser->id)->user(); + + $this->assertInstanceOf(UserItemResult::class, $user); + $this->assertSame($currentUser->id, $user->id); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('im.user.list.get returns profiles for all requested user IDs')] + public function testListGet(): void + { + $currentUser = $this->userService->get()->user(); + $this->assertInstanceOf(UserItemResult::class, $currentUser); + + $users = $this->userService->listGet([$currentUser->id])->users(); + + $this->assertNotEmpty($users); + $this->assertContainsOnlyInstancesOf(UserItemResult::class, $users); + + $ids = array_map(static fn(UserItemResult $userItemResult): int => $userItemResult->id, $users); + $this->assertContains($currentUser->id, $ids); + } +} diff --git a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php new file mode 100644 index 00000000..a5652c14 --- /dev/null +++ b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\Bot\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Bot::class)] +class BotTest extends TestCase +{ + private Bot $botService; + + /** + * @var list + */ + private array $registeredBotIds = []; + + #[\Override] + protected function setUp(): void + { + $this->botService = Factory::getServiceBuilder(true)->getIMBotScope()->bot(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->registeredBotIds as $botId) { + try { + $this->botService->unregister($botId); + } catch (BaseException) { + // bot may already be deleted by the test + } + } + + $this->registeredBotIds = []; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Bot.register creates a new bot and returns valid BotItemResult')] + public function testRegister(): void + { + $code = sprintf('test_bot_%s', uniqid('', true)); + + $result = $this->botService->register( + code: $code, + properties: ['name' => 'Test Bot'], + type: BotType::bot, + eventMode: BotEventMode::fetch + ); + + $bot = $result->bot(); + $this->registeredBotIds[] = $bot->id; + + $this->assertGreaterThan(0, $bot->id); + $this->assertSame($code, $bot->code); + $this->assertSame('bot', $bot->type); + $this->assertFalse($bot->isHidden); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Bot.get returns bot information by code')] + public function testGet(): void + { + $code = sprintf('test_bot_%s', uniqid('', true)); + + $registered = $this->botService->register( + code: $code, + properties: ['name' => 'Test Bot'], + ); + $botId = $registered->bot()->id; + $this->registeredBotIds[] = $botId; + + $result = $this->botService->get(code: $code); + $bot = $result->bot(); + + $this->assertSame($botId, $bot->id); + $this->assertSame($code, $bot->code); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Bot.list returns array of bots')] + public function testList(): void + { + $result = $this->botService->list(); + + $this->assertIsArray($result->bots()); + $this->assertIsBool($result->hasNextPage()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Bot.unregister deletes the bot successfully')] + public function testUnregister(): void + { + $code = sprintf('test_bot_%s', uniqid('', true)); + + $registered = $this->botService->register( + code: $code, + properties: ['name' => 'Test Bot'], + ); + $botId = $registered->bot()->id; + + $result = $this->botService->unregister($botId); + + // EmptyResult — no exception means success + $this->assertNotNull($result->getCoreResponse()); + } +} diff --git a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php new file mode 100644 index 00000000..632eef1e --- /dev/null +++ b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\Chat\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Services\IMBot\Chat\Result\ChatLeaveResult; +use Bitrix24\SDK\Services\IMBot\Chat\Service\Chat; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Chat::class)] +class ChatTest extends TestCase +{ + private Chat $chatService; + + private Bot $botService; + + private int $botId = 0; + + /** + * @var list + */ + private array $createdChatIds = []; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $scope = Factory::getServiceBuilder(true)->getIMBotScope(); + $this->chatService = $scope->chat(); + $this->botService = $scope->bot(); + + $code = sprintf('test_chat_bot_%s', uniqid('', true)); + $result = $this->botService->register( + code: $code, + properties: ['name' => 'Chat Test Bot'], + type: BotType::bot, + eventMode: BotEventMode::fetch + ); + $this->botId = $result->bot()->id; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->createdChatIds as $chatId) { + try { + $this->chatService->leave($this->botId, $chatId); + } catch (BaseException) { + // chat may already be left + } + } + + try { + $this->botService->unregister($this->botId); + } catch (BaseException) { + // bot may already be deleted + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Chat.add creates a new chat and returns ChatItemResult')] + public function testAdd(): void + { + $currentUserId = $this->getCurrentUserId(); + $result = $this->chatService->add( + botId: $this->botId, + userIds: [$currentUserId], + title: sprintf('Test Chat %s', uniqid('', true)) + ); + + $chat = $result->chat(); + $this->createdChatIds[] = $chat->id; + + $this->assertGreaterThan(0, $chat->id); + $this->assertNotEmpty($chat->dialogId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Chat.leave removes the bot from the chat')] + public function testLeave(): void + { + $currentUserId = $this->getCurrentUserId(); + $chatId = $this->chatService->add( + botId: $this->botId, + userIds: [$currentUserId], + title: sprintf('Leave Test %s', uniqid('', true)) + )->chat()->id; + + $result = $this->chatService->leave($this->botId, $chatId); + + $this->assertInstanceOf(ChatLeaveResult::class, $result); + $this->assertTrue($result->isSuccess()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + private function getCurrentUserId(): int + { + return (int)$this->chatService->core + ->call('PROFILE')->getResponseData()->getResult()['ID']; + } +} diff --git a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php new file mode 100644 index 00000000..698b8a8e --- /dev/null +++ b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\ChatMessage\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Services\IMBot\Chat\Service\Chat; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Service\Batch; +use Bitrix24\SDK\Services\IMBot\ChatMessage\Service\ChatMessage; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Batch::class)] +class BatchTest extends TestCase +{ + private Bot $botService; + + private Chat $chatService; + + private ChatMessage $chatMessageService; + + private int $botId; + + private string $dialogId; + + private int $chatId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $scope = Factory::getServiceBuilder(true)->getIMBotScope(); + $this->botService = $scope->bot(); + $this->chatService = $scope->chat(); + $this->chatMessageService = $scope->chatMessage(); + + // Register a test bot + $code = sprintf('test_batch_msg_bot_%s', uniqid('', true)); + $botResult = $this->botService->register( + code: $code, + properties: ['name' => 'Test Batch Message Bot'], + type: BotType::bot, + eventMode: BotEventMode::fetch + ); + $this->botId = $botResult->bot()->id; + + // Create a group chat on behalf of the bot + $chatResult = $this->chatService->add( + botId: $this->botId, + userIds: [], + title: sprintf('Batch Test Chat %s', uniqid('', true)) + ); + $chat = $chatResult->chat(); + $this->chatId = $chat->id; + $this->dialogId = $chat->dialogId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + try { + $this->chatService->leave($this->botId, $this->chatId); + } catch (BaseException) { + // chat may already be gone + } + + try { + $this->botService->unregister($this->botId); + } catch (BaseException) { + // bot may already be deleted + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Chat.Message.send batch sends multiple messages and returns IDs')] + public function testBatchSend(): void + { + $messages = [ + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'Batch message 1'], + ], + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'Batch message 2'], + ], + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'Batch message 3'], + ], + ]; + + $ids = []; + foreach ($this->chatMessageService->batch->send($messages) as $result) { + $ids[] = $result->getId(); + } + + $this->assertCount(count($messages), $ids); + foreach ($ids as $id) { + $this->assertGreaterThan(0, $id); + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Chat.Message.delete batch deletes multiple messages')] + public function testBatchDelete(): void + { + // First send some messages to delete + $sendParams = [ + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'To delete 1'], + ], + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'To delete 2'], + ], + ]; + + $messageIds = []; + foreach ($this->chatMessageService->batch->send($sendParams) as $result) { + $messageIds[] = $result->getId(); + } + + $this->assertCount(count($sendParams), $messageIds); + + // Now delete in batch + $deleteParams = array_map( + fn(int $id): array => ['botId' => $this->botId, 'messageId' => $id], + $messageIds + ); + + $deletedCount = 0; + foreach ($this->chatMessageService->batch->delete($deleteParams) as $result) { + $deletedCount++; + } + + $this->assertSame(count($messageIds), $deletedCount); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Chat.Message.update batch updates multiple messages')] + public function testBatchUpdate(): void + { + // First send some messages to update + $sendParams = [ + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'Original 1'], + ], + [ + 'botId' => $this->botId, + 'dialogId' => $this->dialogId, + 'fields' => ['message' => 'Original 2'], + ], + ]; + + $messageIds = []; + foreach ($this->chatMessageService->batch->send($sendParams) as $result) { + $messageIds[] = $result->getId(); + } + + $this->assertCount(count($sendParams), $messageIds); + + // Update them in batch + $updateParams = array_map( + fn(int $id, int $idx): array => [ + 'botId' => $this->botId, + 'messageId' => $id, + 'fields' => ['message' => sprintf('Updated %d', $idx + 1)], + ], + $messageIds, + array_keys($messageIds) + ); + + $updatedCount = 0; + foreach ($this->chatMessageService->batch->update($updateParams) as $result) { + $updatedCount++; + } + + $this->assertSame(count($messageIds), $updatedCount); + } +} diff --git a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php new file mode 100644 index 00000000..c13eb6f7 --- /dev/null +++ b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\Command\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Services\IMBot\Command\Service\Command; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Command::class)] +class CommandTest extends TestCase +{ + private Command $commandService; + + private Bot $botService; + + private int $botId = 0; + + /** + * @var list + */ + private array $registeredCommandIds = []; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $scope = Factory::getServiceBuilder(true)->getIMBotScope(); + $this->commandService = $scope->command(); + $this->botService = $scope->bot(); + + $code = sprintf('test_cmd_bot_%s', uniqid('', true)); + $result = $this->botService->register( + code: $code, + properties: ['name' => 'Command Test Bot'], + type: BotType::bot, + eventMode: BotEventMode::fetch + ); + $this->botId = $result->bot()->id; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + foreach ($this->registeredCommandIds as $commandId) { + try { + $this->commandService->unregister($this->botId, $commandId); + } catch (BaseException) { + // command may already be deleted + } + } + + try { + $this->botService->unregister($this->botId); + } catch (BaseException) { + // bot may already be deleted + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Command.register registers a slash command and returns CommandItemResult')] + public function testRegister(): void + { + $commandName = sprintf('cmd_%s', substr(uniqid('', true), 0, 8)); + + $result = $this->commandService->register( + botId: $this->botId, + command: $commandName, + title: ['en' => 'Test command'], + hidden: true + ); + + $command = $result->command(); + $this->registeredCommandIds[] = $command->id; + + $this->assertGreaterThan(0, $command->id); + $this->assertSame($this->botId, $command->botId); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Command.list returns list of registered commands')] + public function testList(): void + { + $commandName = sprintf('cmd_%s', substr(uniqid('', true), 0, 8)); + $commandId = $this->commandService->register( + botId: $this->botId, + command: $commandName, + title: ['en' => 'Test command'], + hidden: true + )->command()->id; + $this->registeredCommandIds[] = $commandId; + + $result = $this->commandService->list($this->botId); + + $this->assertNotEmpty($result->commands()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Command.unregister removes the command')] + public function testUnregister(): void + { + $commandName = sprintf('cmd_%s', substr(uniqid('', true), 0, 8)); + $commandId = $this->commandService->register( + botId: $this->botId, + command: $commandName, + title: ['en' => 'Test command'], + hidden: true + )->command()->id; + + $result = $this->commandService->unregister($this->botId, $commandId); + + // EmptyResult — no exception means success + $this->assertNotNull($result->getCoreResponse()); + } +} diff --git a/tests/Integration/Services/IMBot/File/Service/FileTest.php b/tests/Integration/Services/IMBot/File/Service/FileTest.php new file mode 100644 index 00000000..8905f007 --- /dev/null +++ b/tests/Integration/Services/IMBot/File/Service/FileTest.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\File\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Bot\BotEventMode; +use Bitrix24\SDK\Services\IMBot\Bot\BotType; +use Bitrix24\SDK\Services\IMBot\Bot\Service\Bot; +use Bitrix24\SDK\Services\IMBot\Chat\Service\Chat; +use Bitrix24\SDK\Services\IMBot\File\Service\File; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(File::class)] +class FileTest extends TestCase +{ + private Bot $botService; + + private Chat $chatService; + + private File $fileService; + + private int $botId; + + private string $dialogId; + + private int $chatId; + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function setUp(): void + { + $scope = Factory::getServiceBuilder(true)->getIMBotScope(); + $this->botService = $scope->bot(); + $this->chatService = $scope->chat(); + $this->fileService = $scope->file(); + + // Register a test bot + $code = sprintf('test_file_bot_%s', uniqid('', true)); + $botResult = $this->botService->register( + code: $code, + properties: ['name' => 'Test File Bot'], + type: BotType::bot, + eventMode: BotEventMode::fetch + ); + $this->botId = $botResult->bot()->id; + + // Create a group chat on behalf of the bot + $chatResult = $this->chatService->add( + botId: $this->botId, + userIds: [], + title: sprintf('File Test Chat %s', uniqid('', true)) + ); + $chat = $chatResult->chat(); + $this->chatId = $chat->id; + $this->dialogId = $chat->dialogId; + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[\Override] + protected function tearDown(): void + { + try { + $this->chatService->leave($this->botId, $this->chatId); + } catch (BaseException) { + // chat may already be gone + } + + try { + $this->botService->unregister($this->botId); + } catch (BaseException) { + // bot may already be deleted + } + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.File.upload sends a file to a chat and returns FileUploadResult')] + public function testUpload(): void + { + $result = $this->fileService->upload( + botId: $this->botId, + dialogId: $this->dialogId, + name: 'test.txt', + content: base64_encode('IMBot File integration test ' . time()), + message: 'Test file upload' + ); + + $file = $result->file(); + + $this->assertGreaterThan(0, $file->id); + $this->assertNotEmpty($file->name); + $this->assertGreaterThan(0, $file->size); + $this->assertGreaterThan(0, $result->getMessageId()); + $this->assertGreaterThan(0, $result->getChatId()); + $this->assertNotEmpty($result->getDialogId()); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.File.download returns a one-time download URL for an uploaded file')] + public function testDownload(): void + { + // First upload a file to get a file ID + $uploadResult = $this->fileService->upload( + botId: $this->botId, + dialogId: $this->dialogId, + name: 'download_test.txt', + content: base64_encode('IMBot File download test ' . time()) + ); + + $fileId = $uploadResult->file()->id; + $this->assertGreaterThan(0, $fileId); + + // Now get a download URL + $downloadResult = $this->fileService->download( + botId: $this->botId, + fileId: $fileId + ); + + $this->assertNotEmpty($downloadResult->getDownloadUrl()); + $this->assertStringContainsString('http', $downloadResult->getDownloadUrl()); + } +} diff --git a/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php new file mode 100644 index 00000000..ffd13938 --- /dev/null +++ b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Tests\Integration\Services\IMBot\Revision\Service; + +use Bitrix24\SDK\Core\Exceptions\BaseException; +use Bitrix24\SDK\Core\Exceptions\TransportException; +use Bitrix24\SDK\Services\IMBot\Revision\Service\Revision; +use Bitrix24\SDK\Tests\Integration\Fabric; +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\Attributes\TestDox; +use PHPUnit\Framework\TestCase; + +#[CoversClass(Revision::class)] +class RevisionTest extends TestCase +{ + private Revision $revisionService; + + #[\Override] + protected function setUp(): void + { + $this->revisionService = Factory::getServiceBuilder(true)->getIMBotScope()->revision(); + } + + /** + * @throws BaseException + * @throws TransportException + */ + #[Test] + #[TestDox('imbot.v2.Revision.get returns positive revision numbers')] + public function testGet(): void + { + $result = $this->revisionService->get(); + $revision = $result->revision(); + + $this->assertGreaterThan(0, $revision->rest); + $this->assertGreaterThan(0, $revision->web); + $this->assertGreaterThanOrEqual(0, $revision->mobile); + $this->assertGreaterThanOrEqual(0, $revision->desktop); + } +} From bf5365db0775d4320b0cd79e3d38948c11a1b246 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Fri, 3 Jul 2026 18:38:35 +0400 Subject: [PATCH 02/10] Fix BOM (Byte Order Mark) --- src/Services/IM/Chat/Result/ChatItemResult.php | 2 +- src/Services/IM/Counters/Result/CountersItemResult.php | 2 +- src/Services/IM/Department/Result/DepartmentItemResult.php | 2 +- src/Services/IM/Dialog/Result/DialogItemResult.php | 2 +- src/Services/IM/Dialog/Result/DialogReadStateItemResult.php | 2 +- src/Services/IM/Dialog/Result/DialogUserItemResult.php | 2 +- src/Services/IM/Dialog/Result/MessageItemResult.php | 2 +- src/Services/IM/EventV2/Result/EventV2ItemResult.php | 2 +- src/Services/IM/FileV2/Result/FileV2ItemResult.php | 2 +- src/Services/IM/Notify/Result/NotifyItemResult.php | 2 +- src/Services/IM/Notify/Result/NotifySchemaItemResult.php | 2 +- src/Services/IM/Recent/Result/RecentItemResult.php | 2 +- src/Services/IM/Revision/Result/RevisionItemResult.php | 2 +- src/Services/IM/Search/Result/SearchChatItemResult.php | 2 +- src/Services/IM/Search/Result/SearchDepartmentItemResult.php | 2 +- src/Services/IM/Search/Result/SearchUserItemResult.php | 2 +- src/Services/IM/User/Result/UserItemResult.php | 2 +- src/Services/IM/User/Result/UserStatusItemResult.php | 2 +- src/Services/IMBot/Bot/Result/BotItemResult.php | 2 +- src/Services/IMBot/Chat/Result/ChatItemResult.php | 2 +- src/Services/IMBot/ChatMessage/Result/ChatMessageItemResult.php | 2 +- src/Services/IMBot/Command/Result/CommandItemResult.php | 2 +- src/Services/IMBot/Event/Result/EventItemResult.php | 2 +- src/Services/IMBot/File/Result/FileItemResult.php | 2 +- src/Services/IMBot/Revision/Result/RevisionItemResult.php | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Services/IM/Chat/Result/ChatItemResult.php b/src/Services/IM/Chat/Result/ChatItemResult.php index 20b7a021..07eb0800 100644 --- a/src/Services/IM/Chat/Result/ChatItemResult.php +++ b/src/Services/IM/Chat/Result/ChatItemResult.php @@ -1,4 +1,4 @@ - Date: Fri, 3 Jul 2026 19:18:06 +0400 Subject: [PATCH 03/10] Change to using the Fabric class in test classes --- Makefile | 48 +++++++++---------- .../IM/Chat/Result/ChatItemResultTest.php | 4 +- .../Services/IM/Chat/Service/ChatTest.php | 4 +- .../Services/IM/Chat/Service/ChatUserTest.php | 4 +- .../Result/CountersItemResultTest.php | 4 +- .../IM/Counters/Service/CountersTest.php | 4 +- .../DepartmentItemResultAnnotationsTest.php | 4 +- .../IM/Department/Service/DepartmentTest.php | 4 +- .../IM/Dialog/Service/DialogChatTestCase.php | 4 +- .../Services/IM/Disk/Service/DiskTest.php | 8 ++-- .../IM/EventV2/Service/EventV2Test.php | 4 +- .../Services/IM/FileV2/Service/FileV2Test.php | 4 +- .../Message/Service/MessageChatTestCase.php | 4 +- .../IM/Message/Service/MessageTest.php | 4 +- .../NotifyItemResultAnnotationsTest.php | 4 +- .../NotifySchemaItemResultAnnotationsTest.php | 4 +- .../Services/IM/Notify/Service/NotifyTest.php | 4 +- .../Placements/PlacementLocationCodesTest.php | 4 +- .../Services/IM/Placements/PlacementsTest.php | 4 +- .../RecentItemResultAnnotationsTest.php | 4 +- .../Services/IM/Recent/Service/RecentTest.php | 4 +- .../Result/RevisionItemResultTest.php | 4 +- .../IM/Revision/Service/RevisionTest.php | 4 +- .../SearchChatItemResultAnnotationsTest.php | 4 +- ...rchDepartmentItemResultAnnotationsTest.php | 4 +- .../SearchUserItemResultAnnotationsTest.php | 4 +- .../Services/IM/Search/Service/SearchTest.php | 4 +- .../Services/IM/Service/NotifyTest.php | 4 +- .../IM/User/Result/UserItemResultTest.php | 4 +- .../IM/User/Service/UserStatusTest.php | 4 +- .../Services/IM/User/Service/UserTest.php | 4 +- .../Services/IMBot/Bot/Service/BotTest.php | 4 +- .../Services/IMBot/Chat/Service/ChatTest.php | 4 +- .../IMBot/ChatMessage/Service/BatchTest.php | 4 +- .../IMBot/Command/Service/CommandTest.php | 4 +- .../Services/IMBot/File/Service/FileTest.php | 4 +- .../IMBot/Revision/Service/RevisionTest.php | 4 +- 37 files changed, 98 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 9c8f06b2..44536725 100644 --- a/Makefile +++ b/Makefile @@ -513,99 +513,99 @@ test-integration-landing-repowidget: .PHONY: test-integration-scope-imbot test-integration-scope-imbot: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_imbot + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_imbot .PHONY: test-integration-imbot-bot test-integration-imbot-bot: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_bot + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_bot .PHONY: test-integration-imbot-chat test-integration-imbot-chat: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_chat + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_chat .PHONY: test-integration-imbot-command test-integration-imbot-command: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_command + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_command .PHONY: test-integration-imbot-revision test-integration-imbot-revision: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_revision + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_revision .PHONY: test-integration-imbot-chat-message test-integration-imbot-chat-message: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_chat_message + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_chat_message .PHONY: test-integration-imbot-event test-integration-imbot-event: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_event + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_event .PHONY: test-integration-imbot-file test-integration-imbot-file: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_imbot_file + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_imbot_file .PHONY: test-integration-scope-im test-integration-scope-im: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_scope_im + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_scope_im .PHONY: test-integration-im-disk test-integration-im-disk: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_disk + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_disk .PHONY: test-integration-im-message test-integration-im-message: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_message + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_message .PHONY: test-integration-im-dialog test-integration-im-dialog: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_dialog + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_dialog .PHONY: test-integration-im-department test-integration-im-department: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_department + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_department .PHONY: test-integration-im-user test-integration-im-user: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_user + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_user .PHONY: test-integration-im-revision test-integration-im-revision: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_revision + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_revision .PHONY: test-integration-im-counters test-integration-im-counters: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_counters + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_counters .PHONY: test-integration-im-recent test-integration-im-recent: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_recent + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_recent .PHONY: test-integration-im-search test-integration-im-search: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_search + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_search .PHONY: test-integration-im-user-status test-integration-im-user-status: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_user_status + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_user_status .PHONY: test-integration-im-chat test-integration-im-chat: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_chat + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_chat .PHONY: test-integration-im-chat-user test-integration-im-chat-user: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_chat_user + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_chat_user .PHONY: test-integration-im-notify test-integration-im-notify: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_notify + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_notify .PHONY: test-integration-im-event-v2 test-integration-im-event-v2: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_event_v2 + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_event_v2 .PHONY: test-integration-im-file-v2 test-integration-im-file-v2: - docker compose run --rm php-cli $(PHPUNIT) --testsuite integration_tests_im_file_v2 + docker compose run --rm php-cli vendor/bin/phpunit --testsuite integration_tests_im_file_v2 # work dev environment .PHONY: php-dev-server-up diff --git a/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php b/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php index 8fa95fc2..dbfe6d31 100644 --- a/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php +++ b/tests/Integration/Services/IM/Chat/Result/ChatItemResultTest.php @@ -1,4 +1,4 @@ -chatService = Factory::getServiceBuilder()->getIMScope()->chat(); + $this->chatService = Fabric::getServiceBuilder()->getIMScope()->chat(); $currentUserId = (int)$this->chatService->core ->call('PROFILE')->getResponseData()->getResult()['ID']; diff --git a/tests/Integration/Services/IM/Chat/Service/ChatTest.php b/tests/Integration/Services/IM/Chat/Service/ChatTest.php index 151f89d3..710b61ee 100644 --- a/tests/Integration/Services/IM/Chat/Service/ChatTest.php +++ b/tests/Integration/Services/IM/Chat/Service/ChatTest.php @@ -1,4 +1,4 @@ -chatService = Factory::getServiceBuilder()->getIMScope()->chat(); + $this->chatService = Fabric::getServiceBuilder()->getIMScope()->chat(); $this->currentUserId = (int)$this->chatService->core ->call('PROFILE')->getResponseData()->getResult()['ID']; } diff --git a/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php b/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php index f46630a4..f802b964 100644 --- a/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php +++ b/tests/Integration/Services/IM/Chat/Service/ChatUserTest.php @@ -1,4 +1,4 @@ -getIMScope(); + $imServiceBuilder = Fabric::getServiceBuilder()->getIMScope(); $this->chatService = $imServiceBuilder->chat(); $this->chatUserService = $imServiceBuilder->chatUser(); $this->currentUserId = (int)$this->chatUserService->core diff --git a/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php b/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php index 85378fd4..b1b8787c 100644 --- a/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php +++ b/tests/Integration/Services/IM/Counters/Result/CountersItemResultTest.php @@ -1,4 +1,4 @@ -countersService = Factory::getServiceBuilder()->getIMScope()->counters(); + $this->countersService = Fabric::getServiceBuilder()->getIMScope()->counters(); } /** diff --git a/tests/Integration/Services/IM/Counters/Service/CountersTest.php b/tests/Integration/Services/IM/Counters/Service/CountersTest.php index 61750bf4..ccbc560d 100644 --- a/tests/Integration/Services/IM/Counters/Service/CountersTest.php +++ b/tests/Integration/Services/IM/Counters/Service/CountersTest.php @@ -1,4 +1,4 @@ -countersService = Factory::getServiceBuilder()->getIMScope()->counters(); + $this->countersService = Fabric::getServiceBuilder()->getIMScope()->counters(); } /** diff --git a/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php index 9edeb51d..4652fdc5 100644 --- a/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Department/Result/DepartmentItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -departmentService = Factory::getServiceBuilder()->getIMScope()->department(); + $this->departmentService = Fabric::getServiceBuilder()->getIMScope()->department(); } /** diff --git a/tests/Integration/Services/IM/Department/Service/DepartmentTest.php b/tests/Integration/Services/IM/Department/Service/DepartmentTest.php index 21ea52e3..513f8866 100644 --- a/tests/Integration/Services/IM/Department/Service/DepartmentTest.php +++ b/tests/Integration/Services/IM/Department/Service/DepartmentTest.php @@ -1,4 +1,4 @@ -departmentService = Factory::getServiceBuilder()->getIMScope()->department(); + $this->departmentService = Fabric::getServiceBuilder()->getIMScope()->department(); } /** diff --git a/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php b/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php index d2896d4a..dc5ff33d 100644 --- a/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php +++ b/tests/Integration/Services/IM/Dialog/Service/DialogChatTestCase.php @@ -1,4 +1,4 @@ -getIMScope(); + $imServiceBuilder = Fabric::getServiceBuilder()->getIMScope(); $this->dialogService = $imServiceBuilder->dialog(); $this->chatService = $imServiceBuilder->chat(); $this->messageService = $imServiceBuilder->message(); diff --git a/tests/Integration/Services/IM/Disk/Service/DiskTest.php b/tests/Integration/Services/IM/Disk/Service/DiskTest.php index d07ca68b..12703b8f 100644 --- a/tests/Integration/Services/IM/Disk/Service/DiskTest.php +++ b/tests/Integration/Services/IM/Disk/Service/DiskTest.php @@ -1,4 +1,4 @@ -diskService = Factory::getServiceBuilder()->getIMScope()->disk(); - $this->folderService = Factory::getServiceBuilder()->getDiskScope()->folder(); - $this->fileService = Factory::getServiceBuilder()->getDiskScope()->file(); + $this->diskService = Fabric::getServiceBuilder()->getIMScope()->disk(); + $this->folderService = Fabric::getServiceBuilder()->getDiskScope()->folder(); + $this->fileService = Fabric::getServiceBuilder()->getDiskScope()->file(); } /** diff --git a/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php index 9237ab7a..c3a741d9 100644 --- a/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php +++ b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php @@ -1,4 +1,4 @@ -eventService = Factory::getServiceBuilder()->getIMScope()->eventV2(); + $this->eventService = Fabric::getServiceBuilder()->getIMScope()->eventV2(); } /** diff --git a/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php index 85fd732b..35b85121 100644 --- a/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php +++ b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php @@ -1,4 +1,4 @@ -fileService = Factory::getServiceBuilder()->getIMScope()->fileV2(); + $this->fileService = Fabric::getServiceBuilder()->getIMScope()->fileV2(); } /** diff --git a/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php b/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php index 1633ac41..d3009e5e 100644 --- a/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php +++ b/tests/Integration/Services/IM/Message/Service/MessageChatTestCase.php @@ -1,4 +1,4 @@ -getIMScope(); + $imServiceBuilder = Fabric::getServiceBuilder()->getIMScope(); $this->messageService = $imServiceBuilder->message(); $this->chatService = $imServiceBuilder->chat(); $profile = $this->messageService->core->call('PROFILE') diff --git a/tests/Integration/Services/IM/Message/Service/MessageTest.php b/tests/Integration/Services/IM/Message/Service/MessageTest.php index d6247c39..3039d8fe 100644 --- a/tests/Integration/Services/IM/Message/Service/MessageTest.php +++ b/tests/Integration/Services/IM/Message/Service/MessageTest.php @@ -1,4 +1,4 @@ -service = Factory::getServiceBuilder()->getIMScope()->message(); + $this->service = Fabric::getServiceBuilder()->getIMScope()->message(); $this->currentUserId = (int)$this->service->core->call('PROFILE') ->getResponseData()->getResult()['ID']; } diff --git a/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php index 2f502e08..d191f392 100644 --- a/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Notify/Result/NotifyItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -notifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->notifyService = Fabric::getServiceBuilder()->getIMScope()->notify(); $this->currentUserId = (int)$this->notifyService->core->call('PROFILE') ->getResponseData()->getResult()['ID']; } diff --git a/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php index 857d5a49..f5fa0d78 100644 --- a/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Notify/Result/NotifySchemaItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -notifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->notifyService = Fabric::getServiceBuilder()->getIMScope()->notify(); } } diff --git a/tests/Integration/Services/IM/Notify/Service/NotifyTest.php b/tests/Integration/Services/IM/Notify/Service/NotifyTest.php index f4cadadf..d899889f 100644 --- a/tests/Integration/Services/IM/Notify/Service/NotifyTest.php +++ b/tests/Integration/Services/IM/Notify/Service/NotifyTest.php @@ -1,4 +1,4 @@ -imNotifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->imNotifyService = Fabric::getServiceBuilder()->getIMScope()->notify(); $this->currentUserId = (int)$this->imNotifyService->core->call('PROFILE') ->getResponseData()->getResult()['ID']; } diff --git a/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php b/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php index d54f18f1..0956c66f 100644 --- a/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php +++ b/tests/Integration/Services/IM/Placements/PlacementLocationCodesTest.php @@ -1,4 +1,4 @@ -sb = Factory::getServiceBuilder(true); + $this->sb = Fabric::getServiceBuilder(true); } } diff --git a/tests/Integration/Services/IM/Placements/PlacementsTest.php b/tests/Integration/Services/IM/Placements/PlacementsTest.php index c6a05550..4b51d98d 100644 --- a/tests/Integration/Services/IM/Placements/PlacementsTest.php +++ b/tests/Integration/Services/IM/Placements/PlacementsTest.php @@ -1,4 +1,4 @@ -sb = Factory::getServiceBuilder(true); + $this->sb = Fabric::getServiceBuilder(true); } } diff --git a/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php index 43ecb13b..061de5fc 100644 --- a/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Recent/Result/RecentItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -recentService = Factory::getServiceBuilder()->getIMScope()->recent(); + $this->recentService = Fabric::getServiceBuilder()->getIMScope()->recent(); } /** diff --git a/tests/Integration/Services/IM/Recent/Service/RecentTest.php b/tests/Integration/Services/IM/Recent/Service/RecentTest.php index 776a5a6a..30b92c1f 100644 --- a/tests/Integration/Services/IM/Recent/Service/RecentTest.php +++ b/tests/Integration/Services/IM/Recent/Service/RecentTest.php @@ -1,4 +1,4 @@ -recentService = Factory::getServiceBuilder()->getIMScope()->recent(); + $this->recentService = Fabric::getServiceBuilder()->getIMScope()->recent(); } /** diff --git a/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php b/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php index 378e7595..f736c99a 100644 --- a/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php +++ b/tests/Integration/Services/IM/Revision/Result/RevisionItemResultTest.php @@ -1,4 +1,4 @@ -revisionService = Factory::getServiceBuilder()->getIMScope()->revision(); + $this->revisionService = Fabric::getServiceBuilder()->getIMScope()->revision(); } /** diff --git a/tests/Integration/Services/IM/Revision/Service/RevisionTest.php b/tests/Integration/Services/IM/Revision/Service/RevisionTest.php index 69f3a1b2..d5199222 100644 --- a/tests/Integration/Services/IM/Revision/Service/RevisionTest.php +++ b/tests/Integration/Services/IM/Revision/Service/RevisionTest.php @@ -1,4 +1,4 @@ -revisionService = Factory::getServiceBuilder()->getIMScope()->revision(); + $this->revisionService = Fabric::getServiceBuilder()->getIMScope()->revision(); } /** diff --git a/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php index 899d6091..656b3b4f 100644 --- a/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Search/Result/SearchChatItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -searchService = Factory::getServiceBuilder()->getIMScope()->search(); + $this->searchService = Fabric::getServiceBuilder()->getIMScope()->search(); } /** diff --git a/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php index 4a3ec16d..5358fc0c 100644 --- a/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Search/Result/SearchDepartmentItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -searchService = Factory::getServiceBuilder()->getIMScope()->search(); + $this->searchService = Fabric::getServiceBuilder()->getIMScope()->search(); } /** diff --git a/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php b/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php index 38815ad0..696ecb46 100644 --- a/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php +++ b/tests/Integration/Services/IM/Search/Result/SearchUserItemResultAnnotationsTest.php @@ -1,4 +1,4 @@ -searchService = Factory::getServiceBuilder()->getIMScope()->search(); + $this->searchService = Fabric::getServiceBuilder()->getIMScope()->search(); } /** diff --git a/tests/Integration/Services/IM/Search/Service/SearchTest.php b/tests/Integration/Services/IM/Search/Service/SearchTest.php index 7bd121f3..931adc55 100644 --- a/tests/Integration/Services/IM/Search/Service/SearchTest.php +++ b/tests/Integration/Services/IM/Search/Service/SearchTest.php @@ -1,4 +1,4 @@ -searchService = Factory::getServiceBuilder()->getIMScope()->search(); + $this->searchService = Fabric::getServiceBuilder()->getIMScope()->search(); } /** diff --git a/tests/Integration/Services/IM/Service/NotifyTest.php b/tests/Integration/Services/IM/Service/NotifyTest.php index 0c70aaca..7d1d01ef 100644 --- a/tests/Integration/Services/IM/Service/NotifyTest.php +++ b/tests/Integration/Services/IM/Service/NotifyTest.php @@ -1,4 +1,4 @@ -imNotifyService = Factory::getServiceBuilder()->getIMScope()->notify(); + $this->imNotifyService = Fabric::getServiceBuilder()->getIMScope()->notify(); } } \ No newline at end of file diff --git a/tests/Integration/Services/IM/User/Result/UserItemResultTest.php b/tests/Integration/Services/IM/User/Result/UserItemResultTest.php index 0c49c330..aae02098 100644 --- a/tests/Integration/Services/IM/User/Result/UserItemResultTest.php +++ b/tests/Integration/Services/IM/User/Result/UserItemResultTest.php @@ -1,4 +1,4 @@ -userService = Factory::getServiceBuilder()->getIMScope()->user(); + $this->userService = Fabric::getServiceBuilder()->getIMScope()->user(); } /** diff --git a/tests/Integration/Services/IM/User/Service/UserStatusTest.php b/tests/Integration/Services/IM/User/Service/UserStatusTest.php index 93b5b376..432cd9d3 100644 --- a/tests/Integration/Services/IM/User/Service/UserStatusTest.php +++ b/tests/Integration/Services/IM/User/Service/UserStatusTest.php @@ -1,4 +1,4 @@ -userStatusService = Factory::getServiceBuilder()->getIMScope()->userStatus(); + $this->userStatusService = Fabric::getServiceBuilder()->getIMScope()->userStatus(); } /** diff --git a/tests/Integration/Services/IM/User/Service/UserTest.php b/tests/Integration/Services/IM/User/Service/UserTest.php index bd1ec7fa..851bfed1 100644 --- a/tests/Integration/Services/IM/User/Service/UserTest.php +++ b/tests/Integration/Services/IM/User/Service/UserTest.php @@ -1,4 +1,4 @@ -userService = Factory::getServiceBuilder()->getIMScope()->user(); + $this->userService = Fabric::getServiceBuilder()->getIMScope()->user(); } /** diff --git a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php index a5652c14..f5c69e8c 100644 --- a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php +++ b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php @@ -1,4 +1,4 @@ -botService = Factory::getServiceBuilder(true)->getIMBotScope()->bot(); + $this->botService = Fabric::getServiceBuilder(true)->getIMBotScope()->bot(); } /** diff --git a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php index 632eef1e..8db9ff30 100644 --- a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php +++ b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php @@ -1,4 +1,4 @@ -getIMBotScope(); + $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); $this->chatService = $scope->chat(); $this->botService = $scope->bot(); diff --git a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php index 698b8a8e..5c818c12 100644 --- a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php +++ b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php @@ -1,4 +1,4 @@ -getIMBotScope(); + $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); $this->botService = $scope->bot(); $this->chatService = $scope->chat(); $this->chatMessageService = $scope->chatMessage(); diff --git a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php index c13eb6f7..6b8e221f 100644 --- a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php +++ b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php @@ -1,4 +1,4 @@ -getIMBotScope(); + $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); $this->commandService = $scope->command(); $this->botService = $scope->bot(); diff --git a/tests/Integration/Services/IMBot/File/Service/FileTest.php b/tests/Integration/Services/IMBot/File/Service/FileTest.php index 8905f007..460adf9a 100644 --- a/tests/Integration/Services/IMBot/File/Service/FileTest.php +++ b/tests/Integration/Services/IMBot/File/Service/FileTest.php @@ -1,4 +1,4 @@ -getIMBotScope(); + $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); $this->botService = $scope->bot(); $this->chatService = $scope->chat(); $this->fileService = $scope->file(); diff --git a/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php index ffd13938..0efd9991 100644 --- a/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php +++ b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php @@ -1,4 +1,4 @@ -revisionService = Factory::getServiceBuilder(true)->getIMBotScope()->revision(); + $this->revisionService = Fabric::getServiceBuilder(true)->getIMBotScope()->revision(); } /** From e56946a24842a0490a3b4eb8c8aa8e09fd21b838 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Fri, 3 Jul 2026 22:58:51 +0400 Subject: [PATCH 04/10] Fix on phpstan results --- CHANGELOG.md | 9 ++++ src/Core/Contracts/LangCodes.php | 44 +++++++++++++++++++ .../Placement/ExtranetAvailability.php | 29 ++++++++++++ .../Placement/PlacementOptionsInterface.php | 32 ++++++++++++++ src/Services/Placement/Role.php | 31 +++++++++++++ src/Services/Placement/Service/Placement.php | 5 ++- .../Services/IM/Dialog/Service/DialogTest.php | 2 +- 7 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/Core/Contracts/LangCodes.php create mode 100644 src/Services/Placement/ExtranetAvailability.php create mode 100644 src/Services/Placement/PlacementOptionsInterface.php create mode 100644 src/Services/Placement/Role.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc7405c..cefa72af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### Added +- Added `Bitrix24\SDK\Core\Contracts\LangCodes` backed enum with all Bitrix24-supported language codes (`ar`, `de`, `en`, `fr`, `id`, `it`, `ja`, `ko`, `ms`, `pl`, `pt`, `ru`, `sk`, `th`, `tr`, `ua`, `vi`, `zh`) for use in `LANG_ALL` sections of `placement.bind` and similar API calls +- Added `Bitrix24\SDK\Services\Placement\PlacementOptionsInterface` — contract for typed placement OPTIONS builders exposing `build(): array` +- Added `Bitrix24\SDK\Services\Placement\Role` backed enum (`USER`, `ADMIN`) representing user-role filter for IM placement widgets +- Added `Bitrix24\SDK\Services\Placement\ExtranetAvailability` backed enum (`No`/`Y`, `Yes`/`Y`) controlling extranet-user visibility of placement widgets + - Added service `Services\IMBot` scope with support for `imbot.v2.*` methods, see [imbot.v2.* methods](https://apidocs.bitrix24.com/api-reference/chat-bots/chat-bots-v2/index.html) ([#505](https://github.com/bitrix24/b24phpsdk/issues/505)): - `Bot::register` registers a new chat-bot (`imbot.v2.Bot.register`) @@ -224,6 +229,10 @@ All classes under `Bitrix24\SDK\Legacy\` are marked `@deprecated` and will be removed once v3 reaches feature parity with v1. +### Changed + +- `Bitrix24\SDK\Services\Placement\Service\Placement::bind()` — `$options` parameter now accepts `PlacementOptionsInterface|array` (previously `array` only), enabling typed fluent options builders to be passed directly + ## 3.0.0 - 2026.01.01 ### Added diff --git a/src/Core/Contracts/LangCodes.php b/src/Core/Contracts/LangCodes.php new file mode 100644 index 00000000..74e3f546 --- /dev/null +++ b/src/Core/Contracts/LangCodes.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Core\Contracts; + +/** + * Language codes supported by Bitrix24 portal. + * + * Used in `LANG_ALL` section of `placement.bind` and similar API calls. + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/placement-bind.html + */ +enum LangCodes: string +{ + case AR = 'ar'; + case DE = 'de'; + case EN = 'en'; + case FR = 'fr'; + case ID = 'id'; + case IT = 'it'; + case JA = 'ja'; + case KO = 'ko'; + case MS = 'ms'; + case PL = 'pl'; + case PT = 'pt'; + case RU = 'ru'; + case SK = 'sk'; + case TH = 'th'; + case TR = 'tr'; + case UA = 'ua'; + case VI = 'vi'; + case ZH = 'zh'; +} + diff --git a/src/Services/Placement/ExtranetAvailability.php b/src/Services/Placement/ExtranetAvailability.php new file mode 100644 index 00000000..f03b4521 --- /dev/null +++ b/src/Services/Placement/ExtranetAvailability.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Placement; + +/** + * Controls extranet-user availability for a placement widget. + * + * @link https://apidocs.bitrix24.com/api-reference/chats/widgets/placement-info.html + */ +enum ExtranetAvailability: string +{ + /** Not available to extranet users (default). */ + case No = 'N'; + + /** Available to extranet users. */ + case Yes = 'Y'; +} + diff --git a/src/Services/Placement/PlacementOptionsInterface.php b/src/Services/Placement/PlacementOptionsInterface.php new file mode 100644 index 00000000..d2b9b7c6 --- /dev/null +++ b/src/Services/Placement/PlacementOptionsInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Placement; + +/** + * Interface for typed placement OPTIONS builders. + * + * Implementations build the `OPTIONS` array passed to `placement.bind`. + * + * @link https://apidocs.bitrix24.com/api-reference/widgets/placement-bind.html + */ +interface PlacementOptionsInterface +{ + /** + * Build and return the OPTIONS array for placement.bind. + * + * @return array + */ + public function build(): array; +} + diff --git a/src/Services/Placement/Role.php b/src/Services/Placement/Role.php new file mode 100644 index 00000000..c55547b9 --- /dev/null +++ b/src/Services/Placement/Role.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the MIT-LICENSE.txt + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Bitrix24\SDK\Services\Placement; + +/** + * User role filter for IM placement widgets. + * + * Controls which user roles can see the placement widget. + * + * @link https://apidocs.bitrix24.com/api-reference/chats/widgets/placement-info.html + */ +enum Role: string +{ + /** Visible to all users (default). */ + case USER = 'USER'; + + /** Visible to portal administrators only. */ + case ADMIN = 'ADMIN'; +} + diff --git a/src/Services/Placement/Service/Placement.php b/src/Services/Placement/Service/Placement.php index 269cb4bb..9899a19a 100644 --- a/src/Services/Placement/Service/Placement.php +++ b/src/Services/Placement/Service/Placement.php @@ -19,6 +19,7 @@ use Bitrix24\SDK\Core\Exceptions\BaseException; use Bitrix24\SDK\Core\Exceptions\TransportException; use Bitrix24\SDK\Services\AbstractService; +use Bitrix24\SDK\Services\Placement\PlacementOptionsInterface; use Bitrix24\SDK\Services\Placement\Result\PlacementBindResult; use Bitrix24\SDK\Services\Placement\Result\PlacementLocationCodesResult; use Bitrix24\SDK\Services\Placement\Result\PlacementsLocationInformationResult; @@ -44,7 +45,7 @@ public function bind( string $placementCode, string $handlerUrl, array $lang, - array $options = [], + PlacementOptionsInterface|array $options = [], ?int $b24UserId = null, ): PlacementBindResult { return new PlacementBindResult( @@ -54,7 +55,7 @@ public function bind( 'PLACEMENT' => $placementCode, 'HANDLER' => $handlerUrl, 'LANG_ALL' => $lang, - 'OPTIONS' => $options, + 'OPTIONS' => $options instanceof PlacementOptionsInterface ? $options->build() : $options, 'USER_ID' => $b24UserId, ] ) diff --git a/tests/Integration/Services/IM/Dialog/Service/DialogTest.php b/tests/Integration/Services/IM/Dialog/Service/DialogTest.php index 15a6512c..dc443c4d 100644 --- a/tests/Integration/Services/IM/Dialog/Service/DialogTest.php +++ b/tests/Integration/Services/IM/Dialog/Service/DialogTest.php @@ -1,4 +1,4 @@ - Date: Mon, 6 Jul 2026 11:22:07 +0400 Subject: [PATCH 05/10] Fix on rector results --- src/Services/IMBot/Bot/Service/Bot.php | 14 +++++----- src/Services/IMBot/Chat/Service/Chat.php | 6 ++-- .../IM/EventV2/Service/EventV2Test.php | 18 ++++++------ .../Services/IM/FileV2/Service/FileV2Test.php | 20 ++++++------- .../Services/IMBot/Bot/Service/BotTest.php | 26 ++++++++--------- .../Services/IMBot/Chat/Service/ChatTest.php | 24 ++++++++-------- .../IMBot/ChatMessage/Service/BatchTest.php | 8 +++--- .../IMBot/Command/Service/CommandTest.php | 26 ++++++++--------- .../Services/IMBot/File/Service/FileTest.php | 28 +++++++++---------- .../IMBot/Revision/Service/RevisionTest.php | 4 +-- 10 files changed, 87 insertions(+), 87 deletions(-) diff --git a/src/Services/IMBot/Bot/Service/Bot.php b/src/Services/IMBot/Bot/Service/Bot.php index 35b12df0..acf4e993 100644 --- a/src/Services/IMBot/Bot/Service/Bot.php +++ b/src/Services/IMBot/Bot/Service/Bot.php @@ -50,19 +50,19 @@ public function register( string $code, array $properties, ?string $botToken = null, - BotType $type = BotType::bot, - BotEventMode $eventMode = BotEventMode::fetch, + BotType $botType = BotType::bot, + BotEventMode $botEventMode = BotEventMode::fetch, ?string $webhookUrl = null, bool $isHidden = false, bool $isReactionsEnabled = true, bool $isSupportOpenline = false, - ?BotBackground $backgroundId = null, + ?BotBackground $botBackground = null, ): BotResult { $fields = [ 'code' => $code, 'properties' => $properties, - 'type' => $type->value, - 'eventMode' => $eventMode->value, + 'type' => $botType->value, + 'eventMode' => $botEventMode->value, 'isHidden' => $isHidden, 'isReactionsEnabled' => $isReactionsEnabled, 'isSupportOpenline' => $isSupportOpenline, @@ -76,8 +76,8 @@ public function register( $fields['webhookUrl'] = $webhookUrl; } - if ($backgroundId instanceof BotBackground) { - $fields['backgroundId'] = $backgroundId->value; + if ($botBackground instanceof BotBackground) { + $fields['backgroundId'] = $botBackground->value; } return new BotResult($this->core->call('imbot.v2.Bot.register', ['fields' => $fields])); diff --git a/src/Services/IMBot/Chat/Service/Chat.php b/src/Services/IMBot/Chat/Service/Chat.php index f1caa228..c68a8710 100644 --- a/src/Services/IMBot/Chat/Service/Chat.php +++ b/src/Services/IMBot/Chat/Service/Chat.php @@ -47,7 +47,7 @@ public function add( int $botId, array $userIds, ?string $title = null, - ?ChatColor $color = null, + ?ChatColor $chatColor = null, array $fields = [], ?string $botToken = null, ): ChatResult { @@ -57,8 +57,8 @@ public function add( $chatFields['title'] = $title; } - if ($color instanceof ChatColor) { - $chatFields['color'] = $color->value; + if ($chatColor instanceof ChatColor) { + $chatFields['color'] = $chatColor->value; } $params = [ diff --git a/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php index c3a741d9..4390fcdc 100644 --- a/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php +++ b/tests/Integration/Services/IM/EventV2/Service/EventV2Test.php @@ -42,10 +42,10 @@ protected function setUp(): void #[TestDox('im.v2.Event.subscribe returns success')] public function testSubscribe(): void { - $result = $this->eventService->subscribe(); + $emptyResult = $this->eventService->subscribe(); // EmptyResult — no exception means success - $this->assertNotNull($result->getCoreResponse()); + $this->assertNotNull($emptyResult->getCoreResponse()); } /** @@ -57,10 +57,10 @@ public function testSubscribe(): void public function testUnsubscribe(): void { $this->eventService->subscribe(); - $result = $this->eventService->unsubscribe(); + $emptyResult = $this->eventService->unsubscribe(); // EmptyResult — no exception means success - $this->assertNotNull($result->getCoreResponse()); + $this->assertNotNull($emptyResult->getCoreResponse()); } /** @@ -72,13 +72,13 @@ public function testUnsubscribe(): void public function testGet(): void { $this->eventService->subscribe(); - $result = $this->eventService->get(0); + $eventsV2Result = $this->eventService->get(0); - $this->assertInstanceOf(EventsV2Result::class, $result); - $this->assertIsArray($result->getEvents()); - $this->assertIsBool($result->isHasMore()); + $this->assertInstanceOf(EventsV2Result::class, $eventsV2Result); + $this->assertIsArray($eventsV2Result->getEvents()); + $this->assertIsBool($eventsV2Result->isHasMore()); // nextOffset may be null when there are no events - $nextOffset = $result->getNextOffset(); + $nextOffset = $eventsV2Result->getNextOffset(); $this->assertTrue($nextOffset === null || is_int($nextOffset)); } } diff --git a/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php index 35b85121..1edd3171 100644 --- a/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php +++ b/tests/Integration/Services/IM/FileV2/Service/FileV2Test.php @@ -86,21 +86,21 @@ public function testUpload(): void { $dialogId = $this->createChat(); - $result = $this->fileService->upload( + $fileV2UploadResult = $this->fileService->upload( dialogId: $dialogId, name: 'test.txt', content: base64_encode('IM FileV2 integration test ' . time()), message: 'Test file upload' ); - $file = $result->file(); + $file = $fileV2UploadResult->file(); $this->assertGreaterThan(0, $file->id); $this->assertNotEmpty($file->name); $this->assertGreaterThan(0, $file->size); - $this->assertGreaterThan(0, $result->getMessageId()); - $this->assertGreaterThan(0, $result->getChatId()); - $this->assertNotEmpty($result->getDialogId()); + $this->assertGreaterThan(0, $fileV2UploadResult->getMessageId()); + $this->assertGreaterThan(0, $fileV2UploadResult->getChatId()); + $this->assertNotEmpty($fileV2UploadResult->getDialogId()); } /** @@ -114,19 +114,19 @@ public function testDownload(): void $dialogId = $this->createChat(); // First upload a file to get a file ID - $uploadResult = $this->fileService->upload( + $fileV2UploadResult = $this->fileService->upload( dialogId: $dialogId, name: 'download_test.txt', content: base64_encode('IM FileV2 download test ' . time()) ); - $fileId = $uploadResult->file()->id; + $fileId = $fileV2UploadResult->file()->id; $this->assertGreaterThan(0, $fileId); // Now get a download URL - $downloadResult = $this->fileService->download($fileId); + $fileV2DownloadResult = $this->fileService->download($fileId); - $this->assertNotEmpty($downloadResult->getDownloadUrl()); - $this->assertStringContainsString('http', $downloadResult->getDownloadUrl()); + $this->assertNotEmpty($fileV2DownloadResult->getDownloadUrl()); + $this->assertStringContainsString('http', $fileV2DownloadResult->getDownloadUrl()); } } diff --git a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php index f5c69e8c..ef1850d8 100644 --- a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php +++ b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php @@ -47,9 +47,9 @@ protected function setUp(): void #[\Override] protected function tearDown(): void { - foreach ($this->registeredBotIds as $botId) { + foreach ($this->registeredBotIds as $registeredBotId) { try { - $this->botService->unregister($botId); + $this->botService->unregister($registeredBotId); } catch (BaseException) { // bot may already be deleted by the test } @@ -68,14 +68,14 @@ public function testRegister(): void { $code = sprintf('test_bot_%s', uniqid('', true)); - $result = $this->botService->register( + $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test Bot'], type: BotType::bot, eventMode: BotEventMode::fetch ); - $bot = $result->bot(); + $bot = $botResult->bot(); $this->registeredBotIds[] = $bot->id; $this->assertGreaterThan(0, $bot->id); @@ -94,11 +94,11 @@ public function testGet(): void { $code = sprintf('test_bot_%s', uniqid('', true)); - $registered = $this->botService->register( + $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test Bot'], ); - $botId = $registered->bot()->id; + $botId = $botResult->bot()->id; $this->registeredBotIds[] = $botId; $result = $this->botService->get(code: $code); @@ -116,10 +116,10 @@ public function testGet(): void #[TestDox('imbot.v2.Bot.list returns array of bots')] public function testList(): void { - $result = $this->botService->list(); + $botsResult = $this->botService->list(); - $this->assertIsArray($result->bots()); - $this->assertIsBool($result->hasNextPage()); + $this->assertIsArray($botsResult->bots()); + $this->assertIsBool($botsResult->hasNextPage()); } /** @@ -132,15 +132,15 @@ public function testUnregister(): void { $code = sprintf('test_bot_%s', uniqid('', true)); - $registered = $this->botService->register( + $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test Bot'], ); - $botId = $registered->bot()->id; + $botId = $botResult->bot()->id; - $result = $this->botService->unregister($botId); + $emptyResult = $this->botService->unregister($botId); // EmptyResult — no exception means success - $this->assertNotNull($result->getCoreResponse()); + $this->assertNotNull($emptyResult->getCoreResponse()); } } diff --git a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php index 8db9ff30..12d736e7 100644 --- a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php +++ b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php @@ -47,18 +47,18 @@ class ChatTest extends TestCase #[\Override] protected function setUp(): void { - $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); - $this->chatService = $scope->chat(); - $this->botService = $scope->bot(); + $imBotServiceBuilder = Fabric::getServiceBuilder(true)->getIMBotScope(); + $this->chatService = $imBotServiceBuilder->chat(); + $this->botService = $imBotServiceBuilder->bot(); $code = sprintf('test_chat_bot_%s', uniqid('', true)); - $result = $this->botService->register( + $botResult = $this->botService->register( code: $code, properties: ['name' => 'Chat Test Bot'], type: BotType::bot, eventMode: BotEventMode::fetch ); - $this->botId = $result->bot()->id; + $this->botId = $botResult->bot()->id; } /** @@ -68,9 +68,9 @@ protected function setUp(): void #[\Override] protected function tearDown(): void { - foreach ($this->createdChatIds as $chatId) { + foreach ($this->createdChatIds as $createdChatId) { try { - $this->chatService->leave($this->botId, $chatId); + $this->chatService->leave($this->botId, $createdChatId); } catch (BaseException) { // chat may already be left } @@ -92,13 +92,13 @@ protected function tearDown(): void public function testAdd(): void { $currentUserId = $this->getCurrentUserId(); - $result = $this->chatService->add( + $chatResult = $this->chatService->add( botId: $this->botId, userIds: [$currentUserId], title: sprintf('Test Chat %s', uniqid('', true)) ); - $chat = $result->chat(); + $chat = $chatResult->chat(); $this->createdChatIds[] = $chat->id; $this->assertGreaterThan(0, $chat->id); @@ -120,10 +120,10 @@ public function testLeave(): void title: sprintf('Leave Test %s', uniqid('', true)) )->chat()->id; - $result = $this->chatService->leave($this->botId, $chatId); + $chatLeaveResult = $this->chatService->leave($this->botId, $chatId); - $this->assertInstanceOf(ChatLeaveResult::class, $result); - $this->assertTrue($result->isSuccess()); + $this->assertInstanceOf(ChatLeaveResult::class, $chatLeaveResult); + $this->assertTrue($chatLeaveResult->isSuccess()); } /** diff --git a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php index 5c818c12..db0f0f41 100644 --- a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php +++ b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php @@ -49,10 +49,10 @@ class BatchTest extends TestCase #[\Override] protected function setUp(): void { - $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); - $this->botService = $scope->bot(); - $this->chatService = $scope->chat(); - $this->chatMessageService = $scope->chatMessage(); + $imBotServiceBuilder = Fabric::getServiceBuilder(true)->getIMBotScope(); + $this->botService = $imBotServiceBuilder->bot(); + $this->chatService = $imBotServiceBuilder->chat(); + $this->chatMessageService = $imBotServiceBuilder->chatMessage(); // Register a test bot $code = sprintf('test_batch_msg_bot_%s', uniqid('', true)); diff --git a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php index 6b8e221f..762c88b2 100644 --- a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php +++ b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php @@ -46,18 +46,18 @@ class CommandTest extends TestCase #[\Override] protected function setUp(): void { - $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); - $this->commandService = $scope->command(); - $this->botService = $scope->bot(); + $imBotServiceBuilder = Fabric::getServiceBuilder(true)->getIMBotScope(); + $this->commandService = $imBotServiceBuilder->command(); + $this->botService = $imBotServiceBuilder->bot(); $code = sprintf('test_cmd_bot_%s', uniqid('', true)); - $result = $this->botService->register( + $botResult = $this->botService->register( code: $code, properties: ['name' => 'Command Test Bot'], type: BotType::bot, eventMode: BotEventMode::fetch ); - $this->botId = $result->bot()->id; + $this->botId = $botResult->bot()->id; } /** @@ -67,9 +67,9 @@ protected function setUp(): void #[\Override] protected function tearDown(): void { - foreach ($this->registeredCommandIds as $commandId) { + foreach ($this->registeredCommandIds as $registeredCommandId) { try { - $this->commandService->unregister($this->botId, $commandId); + $this->commandService->unregister($this->botId, $registeredCommandId); } catch (BaseException) { // command may already be deleted } @@ -92,14 +92,14 @@ public function testRegister(): void { $commandName = sprintf('cmd_%s', substr(uniqid('', true), 0, 8)); - $result = $this->commandService->register( + $commandResult = $this->commandService->register( botId: $this->botId, command: $commandName, title: ['en' => 'Test command'], hidden: true ); - $command = $result->command(); + $command = $commandResult->command(); $this->registeredCommandIds[] = $command->id; $this->assertGreaterThan(0, $command->id); @@ -123,9 +123,9 @@ public function testList(): void )->command()->id; $this->registeredCommandIds[] = $commandId; - $result = $this->commandService->list($this->botId); + $commandsResult = $this->commandService->list($this->botId); - $this->assertNotEmpty($result->commands()); + $this->assertNotEmpty($commandsResult->commands()); } /** @@ -144,9 +144,9 @@ public function testUnregister(): void hidden: true )->command()->id; - $result = $this->commandService->unregister($this->botId, $commandId); + $emptyResult = $this->commandService->unregister($this->botId, $commandId); // EmptyResult — no exception means success - $this->assertNotNull($result->getCoreResponse()); + $this->assertNotNull($emptyResult->getCoreResponse()); } } diff --git a/tests/Integration/Services/IMBot/File/Service/FileTest.php b/tests/Integration/Services/IMBot/File/Service/FileTest.php index 460adf9a..a72b156d 100644 --- a/tests/Integration/Services/IMBot/File/Service/FileTest.php +++ b/tests/Integration/Services/IMBot/File/Service/FileTest.php @@ -48,10 +48,10 @@ class FileTest extends TestCase #[\Override] protected function setUp(): void { - $scope = Fabric::getServiceBuilder(true)->getIMBotScope(); - $this->botService = $scope->bot(); - $this->chatService = $scope->chat(); - $this->fileService = $scope->file(); + $imBotServiceBuilder = Fabric::getServiceBuilder(true)->getIMBotScope(); + $this->botService = $imBotServiceBuilder->bot(); + $this->chatService = $imBotServiceBuilder->chat(); + $this->fileService = $imBotServiceBuilder->file(); // Register a test bot $code = sprintf('test_file_bot_%s', uniqid('', true)); @@ -102,7 +102,7 @@ protected function tearDown(): void #[TestDox('imbot.v2.File.upload sends a file to a chat and returns FileUploadResult')] public function testUpload(): void { - $result = $this->fileService->upload( + $fileUploadResult = $this->fileService->upload( botId: $this->botId, dialogId: $this->dialogId, name: 'test.txt', @@ -110,14 +110,14 @@ public function testUpload(): void message: 'Test file upload' ); - $file = $result->file(); + $file = $fileUploadResult->file(); $this->assertGreaterThan(0, $file->id); $this->assertNotEmpty($file->name); $this->assertGreaterThan(0, $file->size); - $this->assertGreaterThan(0, $result->getMessageId()); - $this->assertGreaterThan(0, $result->getChatId()); - $this->assertNotEmpty($result->getDialogId()); + $this->assertGreaterThan(0, $fileUploadResult->getMessageId()); + $this->assertGreaterThan(0, $fileUploadResult->getChatId()); + $this->assertNotEmpty($fileUploadResult->getDialogId()); } /** @@ -129,23 +129,23 @@ public function testUpload(): void public function testDownload(): void { // First upload a file to get a file ID - $uploadResult = $this->fileService->upload( + $fileUploadResult = $this->fileService->upload( botId: $this->botId, dialogId: $this->dialogId, name: 'download_test.txt', content: base64_encode('IMBot File download test ' . time()) ); - $fileId = $uploadResult->file()->id; + $fileId = $fileUploadResult->file()->id; $this->assertGreaterThan(0, $fileId); // Now get a download URL - $downloadResult = $this->fileService->download( + $fileDownloadResult = $this->fileService->download( botId: $this->botId, fileId: $fileId ); - $this->assertNotEmpty($downloadResult->getDownloadUrl()); - $this->assertStringContainsString('http', $downloadResult->getDownloadUrl()); + $this->assertNotEmpty($fileDownloadResult->getDownloadUrl()); + $this->assertStringContainsString('http', $fileDownloadResult->getDownloadUrl()); } } diff --git a/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php index 0efd9991..72d2f8e8 100644 --- a/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php +++ b/tests/Integration/Services/IMBot/Revision/Service/RevisionTest.php @@ -41,8 +41,8 @@ protected function setUp(): void #[TestDox('imbot.v2.Revision.get returns positive revision numbers')] public function testGet(): void { - $result = $this->revisionService->get(); - $revision = $result->revision(); + $revisionResult = $this->revisionService->get(); + $revision = $revisionResult->revision(); $this->assertGreaterThan(0, $revision->rest); $this->assertGreaterThan(0, $revision->web); From 2fe1b2c1659548171d2b51570998f91a4480349f Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Mon, 6 Jul 2026 11:31:43 +0400 Subject: [PATCH 06/10] Fix on phpstan results --- tests/Integration/Services/IMBot/Bot/Service/BotTest.php | 4 ++-- tests/Integration/Services/IMBot/Chat/Service/ChatTest.php | 4 ++-- .../Services/IMBot/ChatMessage/Service/BatchTest.php | 4 ++-- .../Services/IMBot/Command/Service/CommandTest.php | 4 ++-- tests/Integration/Services/IMBot/File/Service/FileTest.php | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php index ef1850d8..a1d05403 100644 --- a/tests/Integration/Services/IMBot/Bot/Service/BotTest.php +++ b/tests/Integration/Services/IMBot/Bot/Service/BotTest.php @@ -71,8 +71,8 @@ public function testRegister(): void $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test Bot'], - type: BotType::bot, - eventMode: BotEventMode::fetch + botType: BotType::bot, + botEventMode: BotEventMode::fetch ); $bot = $botResult->bot(); diff --git a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php index 12d736e7..5ad9661c 100644 --- a/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php +++ b/tests/Integration/Services/IMBot/Chat/Service/ChatTest.php @@ -55,8 +55,8 @@ protected function setUp(): void $botResult = $this->botService->register( code: $code, properties: ['name' => 'Chat Test Bot'], - type: BotType::bot, - eventMode: BotEventMode::fetch + botType: BotType::bot, + botEventMode: BotEventMode::fetch ); $this->botId = $botResult->bot()->id; } diff --git a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php index db0f0f41..d54fd109 100644 --- a/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php +++ b/tests/Integration/Services/IMBot/ChatMessage/Service/BatchTest.php @@ -59,8 +59,8 @@ protected function setUp(): void $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test Batch Message Bot'], - type: BotType::bot, - eventMode: BotEventMode::fetch + botType: BotType::bot, + botEventMode: BotEventMode::fetch ); $this->botId = $botResult->bot()->id; diff --git a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php index 762c88b2..28923c20 100644 --- a/tests/Integration/Services/IMBot/Command/Service/CommandTest.php +++ b/tests/Integration/Services/IMBot/Command/Service/CommandTest.php @@ -54,8 +54,8 @@ protected function setUp(): void $botResult = $this->botService->register( code: $code, properties: ['name' => 'Command Test Bot'], - type: BotType::bot, - eventMode: BotEventMode::fetch + botType: BotType::bot, + botEventMode: BotEventMode::fetch ); $this->botId = $botResult->bot()->id; } diff --git a/tests/Integration/Services/IMBot/File/Service/FileTest.php b/tests/Integration/Services/IMBot/File/Service/FileTest.php index a72b156d..94220057 100644 --- a/tests/Integration/Services/IMBot/File/Service/FileTest.php +++ b/tests/Integration/Services/IMBot/File/Service/FileTest.php @@ -58,8 +58,8 @@ protected function setUp(): void $botResult = $this->botService->register( code: $code, properties: ['name' => 'Test File Bot'], - type: BotType::bot, - eventMode: BotEventMode::fetch + botType: BotType::bot, + botEventMode: BotEventMode::fetch ); $this->botId = $botResult->bot()->id; From 2fbaa19befeced945e2af5f5b4f438040cef4bd1 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Mon, 6 Jul 2026 12:35:02 +0400 Subject: [PATCH 07/10] Add case-sensitive methods for imbot.v2.* and im.v2 to ApiClient list --- src/Core/ApiClient.php | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/Core/ApiClient.php b/src/Core/ApiClient.php index e68b5a9b..47e7e02d 100644 --- a/src/Core/ApiClient.php +++ b/src/Core/ApiClient.php @@ -176,6 +176,47 @@ public function getResponse(string $apiMethod, array $parameters = []): Response 'tasks.flow.Flow.isExists', 'tasks.flow.Flow.activate', 'tasks.flow.Flow.pin', + // imbot.v2.* — case-sensitive on the Bitrix24 server side + 'imbot.v2.Bot.get', + 'imbot.v2.Bot.list', + 'imbot.v2.Bot.register', + 'imbot.v2.Bot.unregister', + 'imbot.v2.Bot.update', + 'imbot.v2.Chat.add', + 'imbot.v2.Chat.get', + 'imbot.v2.Chat.leave', + 'imbot.v2.Chat.setOwner', + 'imbot.v2.Chat.update', + 'imbot.v2.Chat.InputAction.notify', + 'imbot.v2.Chat.Manager.add', + 'imbot.v2.Chat.Manager.delete', + 'imbot.v2.Chat.Message.delete', + 'imbot.v2.Chat.Message.get', + 'imbot.v2.Chat.Message.getContext', + 'imbot.v2.Chat.Message.read', + 'imbot.v2.Chat.Message.send', + 'imbot.v2.Chat.Message.update', + 'imbot.v2.Chat.Message.Reaction.add', + 'imbot.v2.Chat.Message.Reaction.delete', + 'imbot.v2.Chat.TextField.enabled', + 'imbot.v2.Chat.User.add', + 'imbot.v2.Chat.User.delete', + 'imbot.v2.Chat.User.list', + 'imbot.v2.Command.answer', + 'imbot.v2.Command.list', + 'imbot.v2.Command.register', + 'imbot.v2.Command.unregister', + 'imbot.v2.Command.update', + 'imbot.v2.Event.get', + 'imbot.v2.File.download', + 'imbot.v2.File.upload', + 'imbot.v2.Revision.get', + // im.v2.* — case-sensitive on the Bitrix24 server side + 'im.v2.Event.get', + 'im.v2.Event.subscribe', + 'im.v2.Event.unsubscribe', + 'im.v2.File.download', + 'im.v2.File.upload', ]; if (!in_array($apiMethod, $caseSensitiveMethods, true)) { $apiMethod = strtolower($apiMethod); From fc6f2d584452063db109095c37a5afe4dd4fbeb4 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Mon, 6 Jul 2026 14:56:42 +0400 Subject: [PATCH 08/10] Add CustomBitrix24Assertions::assertBitrix24ResultItemFieldsTypeCastMatchAnnotations() --- .../CustomBitrix24Assertions.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/CustomAssertions/CustomBitrix24Assertions.php b/tests/CustomAssertions/CustomBitrix24Assertions.php index dd1d32b7..166fb2ea 100644 --- a/tests/CustomAssertions/CustomBitrix24Assertions.php +++ b/tests/CustomAssertions/CustomBitrix24Assertions.php @@ -13,6 +13,7 @@ namespace Bitrix24\SDK\Tests\CustomAssertions; +use Bitrix24\SDK\Core\Result\AbstractItem; use Bitrix24\SDK\Services\CRM\Activity\ActivityContentType; use Bitrix24\SDK\Services\CRM\Activity\ActivityDirectionType; use Bitrix24\SDK\Services\CRM\Activity\ActivityNotifyType; @@ -27,6 +28,57 @@ trait CustomBitrix24Assertions { + /** + * Assert that every property-read field of $resultItemClassName, when accessed via magic getter on $item, + * returns a value whose PHP type matches the PHPDoc annotation. + * + * @param class-string $resultItemClassName + */ + protected function assertBitrix24ResultItemFieldsTypeCastMatchAnnotations( + AbstractItem $item, + string $resultItemClassName + ): void { + $props = TyphoonReflector::build()->reflectClass($resultItemClassName)->properties(); + + foreach ($props as $meta) { + if (!$meta->isAnnotated() || $meta->isNative()) { + continue; + } + + $propName = $meta->id->name; + $typeStr = stringify($meta->type()); + $value = $item->$propName; + + // null is always valid for nullable types + if (str_contains($typeStr, 'null') && $value === null) { + continue; + } + + $message = sprintf( + 'field «%s» in «%s» annotated as «%s» but actual PHP type is «%s»', + $propName, + $resultItemClassName, + $typeStr, + get_debug_type($value) + ); + + // For nullable union types like "Carbon\CarbonImmutable|null", strip null before assertInstanceOf + $classStr = implode('|', array_values(array_filter( + explode('|', $typeStr), + static fn (string $t): bool => $t !== 'null' + ))); + + match (true) { + str_contains($typeStr, 'array') => $this->assertIsArray($value, $message), + str_contains($typeStr, 'bool') => $this->assertIsBool($value, $message), + str_contains($typeStr, 'int') => $this->assertIsInt($value, $message), + str_contains($typeStr, 'float') => $this->assertIsFloat($value, $message), + str_contains($typeStr, 'string') => $this->assertIsString($value, $message), + default => $this->assertInstanceOf($classStr, $value, $message), + }; + } + } + /** * @param array $fieldCodesFromApi * @param class-string $resultItemClassName From 7521c06eba36ebc589cbeb7b6063fa4a9e31661f Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Mon, 6 Jul 2026 18:48:25 +0400 Subject: [PATCH 09/10] Fix on test-integration-im-dialog results --- .../IM/Dialog/Result/DialogItemResult.php | 13 +++++++++++++ .../IM/Dialog/Result/MessageItemResult.php | 16 ++++++++++++++++ src/Services/IM/Recent/Service/Recent.php | 6 +++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Services/IM/Dialog/Result/DialogItemResult.php b/src/Services/IM/Dialog/Result/DialogItemResult.php index ebcb6629..e3f8384d 100644 --- a/src/Services/IM/Dialog/Result/DialogItemResult.php +++ b/src/Services/IM/Dialog/Result/DialogItemResult.php @@ -60,4 +60,17 @@ */ class DialogItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if ($offset === 'date_create' && !empty($this->data[$offset])) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/Dialog/Result/MessageItemResult.php b/src/Services/IM/Dialog/Result/MessageItemResult.php index e3cb6455..07c1af7b 100644 --- a/src/Services/IM/Dialog/Result/MessageItemResult.php +++ b/src/Services/IM/Dialog/Result/MessageItemResult.php @@ -31,6 +31,7 @@ * @property-read string|null $uuid * @property-read array|null $replaces * @property-read array|null $forward + * @property-read array|null $block * @property-read array $params * @property-read bool|null $viewedByOthers * @property-read bool|null $viewed @@ -38,4 +39,19 @@ */ class MessageItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if (($offset === 'date' || $offset === 'disappearing_date') + && !empty($this->data[$offset]) + ) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/Recent/Service/Recent.php b/src/Services/IM/Recent/Service/Recent.php index 5dd9e29b..e61367b4 100644 --- a/src/Services/IM/Recent/Service/Recent.php +++ b/src/Services/IM/Recent/Service/Recent.php @@ -78,7 +78,7 @@ public function list(?int $lastId = null, ?int $limit = null): RecentsResult 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-pin.html', 'Pin or unpin a dialog at the top of the recent list' )] - public function pin(string $dialogId, bool $pin = true): UpdatedItemResult + public function pin(string|int $dialogId, bool $pin = true): UpdatedItemResult { return new UpdatedItemResult($this->core->call('im.recent.pin', [ 'DIALOG_ID' => $dialogId, @@ -95,7 +95,7 @@ public function pin(string $dialogId, bool $pin = true): UpdatedItemResult 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-unread.html', 'Set or remove the unread mark on a dialog' )] - public function unread(string $dialogId, string $action): UpdatedItemResult + public function unread(string|int $dialogId, string $action): UpdatedItemResult { return new UpdatedItemResult($this->core->call('im.recent.unread', [ 'DIALOG_ID' => $dialogId, @@ -112,7 +112,7 @@ public function unread(string $dialogId, string $action): UpdatedItemResult 'https://apidocs.bitrix24.com/api-reference/chats/recent/im-recent-hide.html', 'Remove a dialog from the recent list' )] - public function hide(string $dialogId): UpdatedItemResult + public function hide(string|int $dialogId): UpdatedItemResult { return new UpdatedItemResult($this->core->call('im.recent.hide', [ 'DIALOG_ID' => $dialogId, From 37e80c5cb92b92dd84add9286758b161253ffc55 Mon Sep 17 00:00:00 2001 From: Dmitriy Ignatenko Date: Mon, 6 Jul 2026 19:27:22 +0400 Subject: [PATCH 10/10] Fix on test results --- .../IM/Notify/Result/NotifyItemResult.php | 13 +++++++++++++ .../IM/Recent/Result/RecentItemResult.php | 15 +++++++++++++++ .../IM/Search/Result/SearchChatItemResult.php | 13 +++++++++++++ src/Services/IM/User/Result/UserItemResult.php | 13 +++++++++++++ .../IM/User/Result/UserStatusItemResult.php | 13 +++++++++++++ 5 files changed, 67 insertions(+) diff --git a/src/Services/IM/Notify/Result/NotifyItemResult.php b/src/Services/IM/Notify/Result/NotifyItemResult.php index 70b33e5e..df5a1378 100644 --- a/src/Services/IM/Notify/Result/NotifyItemResult.php +++ b/src/Services/IM/Notify/Result/NotifyItemResult.php @@ -34,4 +34,17 @@ */ class NotifyItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if ($offset === 'date' && !empty($this->data[$offset])) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/Recent/Result/RecentItemResult.php b/src/Services/IM/Recent/Result/RecentItemResult.php index cb0690f3..7b886499 100644 --- a/src/Services/IM/Recent/Result/RecentItemResult.php +++ b/src/Services/IM/Recent/Result/RecentItemResult.php @@ -36,4 +36,19 @@ */ class RecentItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if (($offset === 'date_update' || $offset === 'date_last_activity') + && !empty($this->data[$offset]) + ) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/Search/Result/SearchChatItemResult.php b/src/Services/IM/Search/Result/SearchChatItemResult.php index 5b7d1c74..af591b88 100644 --- a/src/Services/IM/Search/Result/SearchChatItemResult.php +++ b/src/Services/IM/Search/Result/SearchChatItemResult.php @@ -54,4 +54,17 @@ */ class SearchChatItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if ($offset === 'date_create' && !empty($this->data[$offset])) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/User/Result/UserItemResult.php b/src/Services/IM/User/Result/UserItemResult.php index c30b0753..0db8ea17 100644 --- a/src/Services/IM/User/Result/UserItemResult.php +++ b/src/Services/IM/User/Result/UserItemResult.php @@ -48,4 +48,17 @@ */ class UserItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if ($offset === 'last_activity_date' && !empty($this->data[$offset])) { + return CarbonImmutable::createFromFormat(DATE_ATOM, $this->data[$offset]); + } + + return parent::__get($offset); + } } diff --git a/src/Services/IM/User/Result/UserStatusItemResult.php b/src/Services/IM/User/Result/UserStatusItemResult.php index acfcf2f5..7492e3ac 100644 --- a/src/Services/IM/User/Result/UserStatusItemResult.php +++ b/src/Services/IM/User/Result/UserStatusItemResult.php @@ -21,4 +21,17 @@ */ class UserStatusItemResult extends AbstractItem { + /** + * @param int|string $offset + * + * @return mixed + */ + public function __get($offset) + { + if ($offset === 'STATUS' && !empty($this->data[$offset])) { + return UserStatusType::from($this->data[$offset]); + } + + return parent::__get($offset); + } }