Skip to content

Commit c2efb0b

Browse files
committed
feat: add context chat provider
Signed-off-by: Edward Ly <contact@edward.ly>
1 parent d7b3716 commit c2efb0b

2 files changed

Lines changed: 212 additions & 0 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989

9090
final class Application extends App implements IBootstrap {
9191
public const APP_ID = 'mail';
92+
public const CONTEXT_CHAT_MESSAGE_MAX_AGE = intval(60 * 60 * 24 * 365.25); // 1 year
9293

9394
public function __construct(array $urlParams = []) {
9495
parent::__construct(self::APP_ID, $urlParams);
@@ -168,6 +169,15 @@ public function register(IRegistrationContext $context): void {
168169
Horde_Translation::setHandler('Horde_Imap_Client', new HordeTranslationHandler());
169170
Horde_Translation::setHandler('Horde_Mime', new HordeTranslationHandler());
170171
Horde_Translation::setHandler('Horde_Smtp', new HordeTranslationHandler());
172+
173+
// Added in version 5.2.0
174+
if (class_exists('OCA\\ContextChat\\Event\\ContentProviderRegisterEvent')) {
175+
$contextChatProvider = \OCA\Mail\ContextChat\ContextChatProvider::class;
176+
177+
$context->registerEventListener(\OCA\ContextChat\Event\ContentProviderRegisterEvent::class, $contextChatProvider);
178+
$context->registerEventListener(NewMessagesSynchronized::class, $contextChatProvider);
179+
$context->registerEventListener(MessageDeletedEvent::class, $contextChatProvider);
180+
}
171181
}
172182

173183
#[\Override]
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Mail\ContextChat;
11+
12+
use OCA\ContextChat\Event\ContentProviderRegisterEvent;
13+
use OCA\ContextChat\Public\ContentItem;
14+
use OCA\ContextChat\Public\ContentManager;
15+
use OCA\ContextChat\Public\IContentProvider;
16+
use OCA\Mail\AppInfo\Application;
17+
use OCA\Mail\Db\Message;
18+
use OCA\Mail\Db\MessageMapper;
19+
use OCA\Mail\Events\MessageDeletedEvent;
20+
use OCA\Mail\Events\NewMessagesSynchronized;
21+
use OCA\Mail\IMAP\IMAPClientFactory;
22+
use OCA\Mail\Service\AccountService;
23+
use OCA\Mail\Service\MailManager;
24+
use OCP\EventDispatcher\Event;
25+
use OCP\EventDispatcher\IEventListener;
26+
use OCP\IURLGenerator;
27+
use OCP\IUser;
28+
use OCP\IUserManager;
29+
30+
/**
31+
* @implements IEventListener<Event>
32+
*/
33+
class ContextChatProvider implements IContentProvider, IEventListener {
34+
35+
public function __construct(
36+
private AccountService $accountService,
37+
private IMAPClientFactory $clientFactory,
38+
private MailManager $mailManager,
39+
private MessageMapper $messageMapper,
40+
private IURLGenerator $urlGenerator,
41+
private IUserManager $userManager,
42+
private ContentManager $contentManager,
43+
) {
44+
}
45+
46+
public function handle(Event $event): void {
47+
if ($event instanceof ContentProviderRegisterEvent) {
48+
$event->registerContentProvider($this->getAppId(), $this->getId(), self::class);
49+
return;
50+
}
51+
52+
if ($event instanceof NewMessagesSynchronized) {
53+
$account = $event->getAccount();
54+
$mailbox = $event->getMailbox();
55+
$messages = $event->getMessages();
56+
$userId = $account->getUserId();
57+
$client = $this->clientFactory->getClient($account);
58+
59+
$items = [];
60+
foreach ($messages as $message) {
61+
$imapMessage = $this->mailManager->getImapMessage($client, $account, $mailbox, $message->getUid(), true);
62+
63+
// Skip encrypted messages
64+
if ($imapMessage->isEncrypted()) {
65+
continue;
66+
}
67+
68+
$fullMessage = $imapMessage->getFullMessage($imapMessage->getUid(), true);
69+
70+
$items[] = new ContentItem(
71+
(string)$message->getId(),
72+
$this->getId(),
73+
$imapMessage->getSubject(),
74+
$fullMessage['body'] ?? '',
75+
'E-Mail',
76+
$imapMessage->getSentDate(),
77+
[$userId],
78+
);
79+
80+
// Submit 100 items at a time
81+
if (count($items) < 100) {
82+
continue;
83+
}
84+
$this->contentManager->submitContent($this->getAppId(), $items);
85+
$items = [];
86+
}
87+
88+
// Submit remaining items
89+
if ($items) {
90+
$this->contentManager->submitContent($this->getAppId(), $items);
91+
}
92+
return;
93+
}
94+
95+
if ($event instanceof MessageDeletedEvent) {
96+
$this->contentManager->deleteContent($this->getAppId(), $this->getId(), [$event->getMessageId()]);
97+
return;
98+
}
99+
}
100+
101+
/**
102+
* The ID of the provider
103+
*
104+
* @return string
105+
* @since 5.2.0
106+
*/
107+
public function getId(): string {
108+
return 'mail';
109+
}
110+
111+
/**
112+
* The ID of the app making the provider avaialble
113+
*
114+
* @return string
115+
* @since 5.2.0
116+
*/
117+
public function getAppId(): string {
118+
return Application::APP_ID;
119+
}
120+
121+
/**
122+
* The absolute URL to the content item
123+
*
124+
* @param string $id
125+
* @return string
126+
* @since 5.2.0
127+
*/
128+
public function getItemUrl(string $id): string {
129+
// Get mailbox ID from message ID
130+
$messages = $this->messageMapper->findByIds('', [(int)$id], '');
131+
if (!$messages) {
132+
return '';
133+
}
134+
$mailboxId = $messages[0]->getMailboxId();
135+
136+
return $this->urlGenerator->linkToRouteAbsolute('mail.page.thread', [ 'mailboxId' => $mailboxId, 'id' => $id ]);
137+
}
138+
139+
/**
140+
* Starts the initial import of content items into content chat
141+
*
142+
* @return void
143+
* @since 5.2.0
144+
*/
145+
public function triggerInitialImport(): void {
146+
$startTime = time() - Application::CONTEXT_CHAT_MESSAGE_MAX_AGE;
147+
148+
$this->userManager->callForSeenUsers(function (IUser $user) use ($startTime): void {
149+
$userId = $user->getUID();
150+
$userAccounts = $this->accountService->findByUserId($userId);
151+
152+
foreach ($userAccounts as $account) {
153+
$mailboxes = $this->mailManager->getMailboxes($account);
154+
$client = $this->clientFactory->getClient($account);
155+
156+
foreach ($mailboxes as $mailbox) {
157+
$messageUids = $this->messageMapper->findAllUids($mailbox);
158+
$messages = $this->messageMapper->findByUids($mailbox, $messageUids);
159+
160+
$items = [];
161+
foreach ($messages as $message) {
162+
// Skip older messages
163+
if ($message->getSentAt() < $startTime) {
164+
continue;
165+
}
166+
167+
$imapMessage = $this->mailManager->getImapMessage($client, $account, $mailbox, $message->getUid(), true);
168+
169+
// Skip encrypted messages
170+
if ($imapMessage->isEncrypted()) {
171+
continue;
172+
}
173+
174+
$fullMessage = $imapMessage->getFullMessage($imapMessage->getUid(), true);
175+
176+
$items[] = new ContentItem(
177+
(string)$message->getId(),
178+
$this->getId(),
179+
$imapMessage->getSubject(),
180+
$fullMessage['body'] ?? '',
181+
'E-Mail',
182+
$imapMessage->getSentDate(),
183+
[$userId],
184+
);
185+
186+
// Submit 100 items at a time
187+
if (count($items) < 100) {
188+
continue;
189+
}
190+
$this->contentManager->submitContent($this->getAppId(), $items);
191+
$items = [];
192+
}
193+
194+
// Submit remaining items
195+
if ($items) {
196+
$this->contentManager->submitContent($this->getAppId(), $items);
197+
}
198+
}
199+
}
200+
});
201+
}
202+
}

0 commit comments

Comments
 (0)