Experiment: process receipts

pull/194/merge
M66B 3 years ago
parent 7eefed0843
commit a6a2c07d6f

@ -448,6 +448,7 @@ The low priority status bar notification shows the number of pending operations,
* *exists*: check if message exists
* *rule*: execute rule on body text
* *expunge*: permanently delete messages
* *report*: process delivery or read receipt (experimental)
Operations are processed only when there is a connection to the email server or when manually synchronizing.
See also [this FAQ](#user-content-faq16).
@ -3429,6 +3430,30 @@ Remarks:
<br />
*Process delivery/read receipt (version 1.1797+)*
On receiving a delivery or read receipt, the related message will be looked up in the sent messages folder
and the following keywords will be set depending on the contents of the report:
```
$Delivered
$NotDelivered
$Displayed
$NotDisplayed
```
* Delivered: action = *delivered*, *relayed*, or *expanded*, [see here](https://datatracker.ietf.org/doc/html/rfc3464#section-2.3.3)
* Displayed: disposition = *displayed*, [see here](https://datatracker.ietf.org/doc/html/rfc3798#section-3.2.6)
It is probably a good idea to enable *Show keywords in message header* in the display settings.
Note that the email server needs to support IMAP flags (keywords) for this feature.
Filter rules will be applied to the received receipt, so it is possible to move/archive the receipt.
See [this FAQ](#user-content-faq71) for a header condition to recognize receipts.
<br />
<a name="faq126"></a>
**(126) Can message previews be sent to my wearable?**

@ -239,6 +239,7 @@ class Core {
if (message == null &&
!EntityOperation.FETCH.equals(op.name) &&
!EntityOperation.REPORT.equals(op.name) &&
!EntityOperation.SYNC.equals(op.name) &&
!EntityOperation.SUBSCRIBE.equals(op.name) &&
!EntityOperation.PURGE.equals(op.name) &&
@ -346,6 +347,7 @@ class Core {
case EntityOperation.ANSWERED:
case EntityOperation.ADD:
case EntityOperation.REPORT:
// Do nothing
break;
@ -445,6 +447,10 @@ class Core {
onExists(context, jargs, account, folder, message, op, (IMAPFolder) ifolder);
break;
case EntityOperation.REPORT:
onReport(context, jargs, folder, (IMAPStore) istore, (IMAPFolder) ifolder, state);
break;
case EntityOperation.SYNC:
Helper.gc();
onSynchronizeMessages(context, jargs, account, folder, (IMAPStore) istore, (IMAPFolder) ifolder, state);
@ -1970,6 +1976,43 @@ class Core {
}
}
private static void onReport(Context context, JSONArray jargs, EntityFolder folder, IMAPStore istore, IMAPFolder ifolder, State state) throws JSONException, MessagingException {
String msgid = jargs.getString(0);
String keyword = jargs.getString(1);
if (TextUtils.isEmpty(msgid))
throw new IllegalArgumentException("msgid missing");
if (TextUtils.isEmpty(keyword))
throw new IllegalArgumentException("keyword missing");
if (folder.read_only)
throw new IllegalArgumentException(folder.name + " read-only");
if (!ifolder.getPermanentFlags().contains(Flags.Flag.USER))
throw new IllegalArgumentException(folder.name + " has no keywords");
Message[] imessages = ifolder.search(new MessageIDTerm(msgid));
if (imessages == null || imessages.length == 0)
throw new IllegalArgumentException(msgid + " not found");
for (Message imessage : imessages) {
long uid = ifolder.getUID(imessage);
Log.i("Report uid=" + uid + " keyword=" + keyword);
Flags flags = new Flags(keyword);
imessage.setFlags(flags, true);
try {
JSONArray fargs = new JSONArray();
fargs.put(uid);
onFetch(context, fargs, folder, istore, ifolder, state);
} catch (Throwable ex) {
Log.w(ex);
}
}
}
static void onSynchronizeFolders(
Context context, EntityAccount account, Store istore, State state,
boolean keep_alive, boolean force) throws MessagingException {
@ -3564,6 +3607,7 @@ class Core {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean download_headers = prefs.getBoolean("download_headers", false);
boolean notify_known = prefs.getBoolean("notify_known", false);
boolean experiments = prefs.getBoolean("experiments", false);
boolean pro = ActivityBilling.isPro(context);
long uid = ifolder.getUID(imessage);
@ -3863,6 +3907,22 @@ class Core {
List<Header> headers = (needsHeaders ? helper.getAllHeaders() : null);
String body = (needsBody ? helper.getMessageParts().getHtml(context) : null);
if (experiments && helper.isReport())
try {
MessageHelper.Report r = parts.getReport();
EntityFolder s = db.folder().getFolderByType(folder.account, EntityFolder.SENT);
if (r != null && s != null) {
if (r.isDeliveryStatus())
EntityOperation.queue(context, s, EntityOperation.REPORT,
message.inreplyto, r.isDelivered() ? "$Delivered" : "$NotDelivered");
else if (r.isDispositionNotification())
EntityOperation.queue(context, s, EntityOperation.REPORT,
message.inreplyto, r.isDisplayed() ? "$Displayed" : "$NotDisplayed");
}
} catch (Throwable ex) {
Log.w(ex);
}
try {
db.beginTransaction();

@ -39,6 +39,7 @@ public interface DaoOperation {
// Other operations: add, delete, seen, answered, flag, keyword, label, subscribe, send
" WHEN operation.name = '" + EntityOperation.FETCH + "' THEN 2" +
" WHEN operation.name = '" + EntityOperation.EXISTS + "' THEN 3" +
" WHEN operation.name = '" + EntityOperation.REPORT + "' THEN 3" +
" WHEN operation.name = '" + EntityOperation.COPY + "' THEN 4" +
" WHEN operation.name = '" + EntityOperation.MOVE + "' THEN 5" +
" WHEN operation.name = '" + EntityOperation.PURGE + "' THEN 6" +

@ -101,6 +101,7 @@ public class EntityOperation {
static final String RULE = "rule";
static final String PURGE = "purge";
static final String EXPUNGE = "expunge";
static final String REPORT = "report";
private static final int MAX_FETCH = 100; // operations
private static final long FORCE_WITHIN = 30 * 1000; // milliseconds

@ -2598,6 +2598,22 @@ public class MessageHelper {
return sb.toString();
}
Report getReport() throws MessagingException, IOException {
for (PartHolder h : extra)
if (h.isReport()) {
String result;
Object content = h.part.getContent();
if (content instanceof String)
result = (String) content;
else if (content instanceof InputStream)
result = Helper.readStream((InputStream) content);
else
result = content.toString();
return new Report(h.contentType.getBaseType(), result);
}
return null;
}
List<AttachmentPart> getAttachmentParts() {
return attachments;
}
@ -3789,6 +3805,14 @@ public class MessageHelper {
this.html = report.toString();
}
boolean isDeliveryStatus() {
return isDeliveryStatus(type);
}
boolean isDispositionNotification() {
return isDispositionNotification(type);
}
boolean isDelivered() {
return ("delivered".equals(action) || "relayed".equals(action) || "expanded".equals(action));
}

Loading…
Cancel
Save