Skip to content

Commit ad4c23a

Browse files
authored
Merge pull request mjs#335 from growbots/special-folders
Locate special folders like Sent or Trash
2 parents 6dea381 + 052209e commit ad4c23a

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

imapclient/imapclient.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,27 @@
7676
DRAFT = br'\Draft'
7777
RECENT = br'\Recent' # This flag is read-only
7878

79+
# Special folders, see RFC6154
80+
# \Flagged is omitted because it is the same as the flag defined above
81+
ALL = br'\All'
82+
ARCHIVE = br'\Archive'
83+
DRAFTS = br'\Drafts'
84+
JUNK = br'\Junk'
85+
SENT = br'\Sent'
86+
TRASH = br'\Trash'
87+
88+
# Personal namespaces that are common among providers
89+
# used as a fallback when the server does not support the NAMESPACE capability
90+
_POPULAR_PERSONAL_NAMESPACES = (("", ""), ("INBOX.", "."))
91+
92+
# Names of special folders that are common among providers
93+
_POPULAR_SPECIAL_FOLDERS = {
94+
SENT: ("Sent", "Sent Items", "Sent items"),
95+
DRAFTS: ("Drafts",),
96+
ARCHIVE: ("Archive",),
97+
TRASH: ("Trash", "Deleted Items", "Deleted Messages"),
98+
JUNK: ("Junk", "Spam")
99+
}
79100

80101
class Namespace(tuple):
81102

@@ -565,6 +586,42 @@ def _proc_folder_list(self, folder_data):
565586
ret.append((flags, delim, name))
566587
return ret
567588

589+
def find_special_folder(self, folder_flag):
590+
"""Try to locate a special folder, like the Sent or Trash folder.
591+
592+
>>> server.find_special_folder(imapclient.SENT)
593+
'INBOX.Sent'
594+
595+
This function tries its best to find the correct folder (if any) but
596+
uses heuristics when the server is unable to precisely tell where
597+
special folders are located.
598+
599+
Returns the name of the folder if found, or None otherwise.
600+
"""
601+
# Detect folder by looking for known attributes
602+
# TODO: avoid listing all folders by using extended LIST (RFC6154)
603+
if self.has_capability('SPECIAL-USE'):
604+
for folder in self.list_folders():
605+
if folder and len(folder[0]) > 1 and folder[0][1] == folder_flag:
606+
return folder[2]
607+
608+
# Detect folder by looking for common names
609+
# We only look for folders in the "personal" namespace of the user
610+
if self.has_capability('NAMESPACE'):
611+
personal_namespaces = self.namespace().personal
612+
else:
613+
personal_namespaces = _POPULAR_PERSONAL_NAMESPACES
614+
615+
for personal_namespace in personal_namespaces:
616+
for pattern in _POPULAR_SPECIAL_FOLDERS.get(folder_flag, tuple()):
617+
pattern = personal_namespace[0] + pattern
618+
sent_folders = self.list_folders(pattern=pattern)
619+
if sent_folders:
620+
return sent_folders[0][2]
621+
622+
return None
623+
624+
568625
def select_folder(self, folder, readonly=False):
569626
"""Set the current folder on the server.
570627

tests/test_imapclient.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,34 @@ def test_blanks(self):
152152
self.assertEqual(folders, [((br'\HasNoChildren',), b'/', 'last')])
153153

154154

155+
class TestFindSpecialFolder(IMAPClientTest):
156+
157+
def test_find_special_folder_with_special_use(self):
158+
self.client._cached_capabilities = (b'SPECIAL-USE',)
159+
self.client._imap._simple_command.return_value = ('OK', [b'something'])
160+
self.client._imap._untagged_response.return_value = (
161+
'LIST', [
162+
b'(\\HasNoChildren) "/" "INBOX"',
163+
b'(\\HasNoChildren \\Sent) "/" "Sent"',
164+
])
165+
166+
folder = self.client.find_special_folder(b'\\Sent')
167+
168+
self.assertEqual(folder, "Sent")
169+
170+
def test_find_special_folder_without_special_use_nor_namespace(self):
171+
self.client._cached_capabilities = (b'FOO',)
172+
self.client._imap._simple_command.return_value = ('OK', [b'something'])
173+
self.client._imap._untagged_response.return_value = (
174+
'LIST', [
175+
b'(\\HasNoChildren) "/" "Sent Items"',
176+
])
177+
178+
folder = self.client.find_special_folder(b'\\Sent')
179+
180+
self.assertEqual(folder, "Sent Items")
181+
182+
155183
class TestSelectFolder(IMAPClientTest):
156184

157185
def test_normal(self):

0 commit comments

Comments
 (0)