1 /// 2 module kaleidic.sil.plugin.imap.register; 3 4 version (SIL): 5 6 import imap.set; 7 import kaleidic.sil.lang.handlers : Handlers; 8 import kaleidic.sil.lang.typing.types : SILdoc; 9 import std.meta : AliasSeq; 10 11 version (SIL_Plugin) { 12 import kaleidic.sil.lang.plugin : pluginImpl; 13 mixin pluginImpl!registerImap; 14 } 15 16 import std.socket; 17 18 import imap.defines; 19 import imap.socket; 20 21 import arsd.email : MimeContainer; 22 23 /// 24 void registerImap(ref Handlers handlers) { 25 import std.conv; 26 import imap.session; 27 import imap.request; 28 import imap.response; 29 import imap.namespace; 30 import imap.searchquery; 31 import arsd.email : IncomingEmailMessage, RelayInfo, ToType, EmailMessage, MimePart; 32 33 // Register for imap.*. 34 { 35 handlers.openModule("imap"); 36 scope (exit) handlers.closeModule(); 37 38 static foreach (T; AliasSeq!(BodyResponse, EmailMessage, FlagResult, ImapLogin, ImapResult, 39 ImapServer, ImapStatus, IncomingEmailMessage, ListEntry, 40 ListResponse, Mailbox, MailboxList, MimeAttachment, 41 MimeContainer_, MimePart, Options, ProtocolSSL, RelayInfo, 42 Result!string, SearchResult, SearchResultType, Session, Status, 43 StatusResult, StoreMode, ToType 44 )) { 45 handlers.registerType!T; 46 } 47 48 static foreach (F; AliasSeq!(append, attachments, close, closeConnection, copy, create, 49 decodeMimeHeader, delete_, enable, esearch, examine, expunge, 50 fetchDate, fetchFast, fetchFields, fetchFlags, fetchHeader, 51 fetchPart, fetchRFC822, fetchSize, fetchStructure, fetchText, 52 idle, list, login, logout, lsub, move, moveUIDs, multiMove, 53 multiSearch, noop, openConnection, raw, rename, select, status, 54 store, subscribe, unsubscribe, writeBinaryString 55 )) { 56 handlers.registerHandler!F; 57 } 58 59 handlers.registerType!Socket; 60 handlers.registerType!SocketFlags; 61 handlers.registerType!SocketOption; 62 handlers.registerType!SocketOptionLevel; 63 handlers.registerType!SocketShutdown; 64 handlers.registerType!Capability; 65 handlers.registerType!(Set!Capability)("Capabilities"); 66 handlers.registerType!(Set!ulong)("UidSet"); 67 68 handlers.registerHandler!(addSet!ulong)("addUidSet"); 69 handlers.registerHandler!(removeSet!ulong)("removeUidSet"); 70 71 handlers.registerType!SearchQuery("Query"); 72 handlers.registerHandlerOverloads!( 73 ((Session session, SearchQuery query) => session.search(query.to!string)), 74 ((Session session, string str) => session.search(str)), 75 )("search"); 76 77 import jmap : registerHandlersJmap; 78 handlers.registerHandlersJmap(); 79 } 80 81 // Register for imap.query.*. 82 { 83 handlers.openModule("imap.query"); 84 scope (exit) handlers.closeModule(); 85 86 auto opDoc = SILdoc(`Compose search query terms with boolean operations. A query expression can be 87 created with 'and', 'or' and 'not', and also with 'andNot' and 'orNot'. 88 89 Query terms are specific filter criteria such as 'old()' or 90 'from("alice@example.com")'. 91 92 E.g., 93 // Equivalent to (flagged OR subject contains "urgent") AND NOT from "gmail.com". 94 query = imap.Query() 95 |> and(flagged()) 96 |> or(subject("urgent")) 97 |> andNot(from("gmail.com")) 98 99 This query can then be passed to 'imap.search()'. 100 101 When applying an or() operator the passed argument is OR'd with whatever is 102 already in the query. Queries may be nested to enforce a precedence or to 103 essentially introduce parentheses. 104 105 E.g., 106 // Equivalent to NOT flagged AND (seen OR recent) AND from "barry" 107 query = imap.Query() 108 |> not(flagged()) 109 |> and(imap.Query() |> or(seen()) |> or(recent())) 110 |> and(from("barry")) 111 112 NOTE: These operators modify the Query in-place. Be careful when re-using sub-queries: 113 114 a = imap.Query(recent()) // 'a' matches 'recent'. 115 b = a |> and(flagged()) // *Both* 'a' and 'b' now match ("recent" AND "flagged"). 116 117 To use these operators and terms as shown above, use: 118 119 import imap 120 import * from imap.query`); 121 122 // Boolean ops. 123 handlers.registerHandlerOverloads!( 124 (SearchQuery this_, const(SearchExpr) *expr) => this_.and(expr), 125 (SearchQuery this_, const SearchQuery other) => this_.and(other), 126 )("and", opDoc); 127 handlers.registerHandlerOverloads!( 128 (SearchQuery this_, const(SearchExpr) *expr) => this_.or(expr), 129 (SearchQuery this_, const SearchQuery other) => this_.or(other), 130 )("or", opDoc); 131 handlers.registerHandler!((SearchQuery this_, const(SearchExpr) *expr) => this_.not(expr))("not", opDoc); 132 handlers.registerHandlerOverloads!( 133 (SearchQuery this_, const(SearchExpr) *expr) => this_.andNot(expr), 134 (SearchQuery this_, const SearchQuery other) => this_.andNot(other), 135 )("andNot", opDoc); 136 handlers.registerHandlerOverloads!( 137 (SearchQuery this_, const(SearchExpr) *expr) => this_.orNot(expr), 138 (SearchQuery this_, const SearchQuery other) => this_.orNot(other), 139 )("orNot", opDoc); 140 141 auto termDoc = SILdoc(`Search terms used to build a query to pass to imap.search(). Also see imap.query 142 functions, e.g., imap.query.and(). 143 144 Flag terms, where the flag is set or unset: 145 answered(), deleted(), draft(), flagged(), new(), old(), recent(), seen(), 146 unanswered(), undeleted(), undraft(), unflagged, unseen(), keyword(str), 147 unkeyword(str). 148 149 Field terms, where the field contains 'str': 150 bcc(str), body(str), cc(str), from(str), subject(str), text(str), to(str). 151 152 Date terms, where date may be a dates.Date. 153 before(date), on(date), sentBefore(date), sentOn(date), sendSince(date), 154 since(date). 155 156 Size terms, where size is the entire message size in bytes. 157 larger(size), smaller(size).`); 158 159 // Flags. 160 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Answered)))("answered", termDoc); 161 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Deleted)))("deleted", termDoc); 162 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Draft)))("draft", termDoc); 163 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Flagged)))("flagged", termDoc); 164 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.New)))("new", termDoc); 165 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Old)))("old", termDoc); 166 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Recent)))("recent", termDoc); 167 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Seen)))("seen", termDoc); 168 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unanswered)))("unanswered", termDoc); 169 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Undeleted)))("undeleted", termDoc); 170 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Undraft)))("undraft", termDoc); 171 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unflagged)))("unflagged", termDoc); 172 handlers.registerHandler!(() => new const SearchExpr(FlagTerm(FlagTerm.Flag.Unseen)))("unseen", termDoc); 173 174 // Keyword. 175 handlers.registerHandler!((string keyw) => new const SearchExpr(KeywordTerm(keyw)))("keyword", termDoc); 176 handlers.registerHandler!((string keyw) => new const SearchExpr(KeywordTerm(keyw, true)))("unkeyword", termDoc); 177 178 // Fields. 179 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Bcc, str)))("bcc", termDoc); 180 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Body, str)))("body", termDoc); 181 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Cc, str)))("cc", termDoc); 182 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.From, str)))("from", termDoc); 183 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Subject, str)))("subject", termDoc); 184 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.Text, str)))("text", termDoc); 185 handlers.registerHandler!((string str) => new const SearchExpr(FieldTerm(FieldTerm.Field.To, str)))("to", termDoc); 186 187 handlers.registerHandler!((string hdr, string str) => new const SearchExpr(HeaderTerm(hdr, str)))("header", termDoc); 188 189 // Dates. 190 import std.datetime : Date; 191 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.Before, date)))("before", termDoc); 192 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.On, date)))("on", termDoc); 193 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentBefore, date)))("sentBefore", termDoc); 194 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentOn, date)))("sentOn", termDoc); 195 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.SentSince, date)))("sentSince", termDoc); 196 handlers.registerHandler!((Date date) => new const SearchExpr(DateTerm(DateTerm.When.Since, date)))("since", termDoc); 197 198 // Sizes. 199 handlers.registerHandler!((int size) => new const SearchExpr(SizeTerm(SizeTerm.Relation.Larger, size)))("larger", termDoc); 200 handlers.registerHandler!((int size) => new const SearchExpr(SizeTerm(SizeTerm.Relation.Smaller, size)))("smaller", termDoc); 201 202 // UID sequences. 203 // XXX This is tricky. We'd like some simple syntax to be able to declare them in SIL (I 204 // assume? Or do we?) but mostly we'd like to use whatever is returned from other APIs 205 // (most likely prior searches). 206 } 207 } 208 209 void writeBinaryString(string file, string data) { 210 import std.file; 211 write(file, data); 212 } 213 214 struct MimeContainer_ { 215 MimeContainer h; 216 string contentType() { return h._contentType; } 217 string boundary() { return h.boundary; } 218 string[] headers() { return h.headers; } 219 string content() { return h.content; } 220 221 MimeContainer_[] stuff() { 222 import std.algorithm : map; 223 import std.array : array; 224 return h.stuff.map!(s => MimeContainer_(s)).array; 225 } 226 227 this(MimeContainer h) { 228 this.h = h; 229 } 230 } 231 232 MimeContainer_ accessMimeContainer(MimeContainer mimeContainer) { 233 return MimeContainer_(mimeContainer); 234 } 235