mirror of
https://github.com/coddingtonbear/django-mailbox.git
synced 2026-07-09 22:38:19 +02:00
wrong folder stage
This commit is contained in:
parent
a52f20dabe
commit
50ee86d1d9
69 changed files with 4191 additions and 85 deletions
11
build/lib/django_mailbox/transports/__init__.py
Normal file
11
build/lib/django_mailbox/transports/__init__.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# all imports below are only used by external modules
|
||||
# flake8: noqa
|
||||
from django_mailbox.transports.imap import ImapTransport
|
||||
from django_mailbox.transports.pop3 import Pop3Transport
|
||||
from django_mailbox.transports.maildir import MaildirTransport
|
||||
from django_mailbox.transports.mbox import MboxTransport
|
||||
from django_mailbox.transports.babyl import BabylTransport
|
||||
from django_mailbox.transports.mh import MHTransport
|
||||
from django_mailbox.transports.mmdf import MMDFTransport
|
||||
from django_mailbox.transports.gmail import GmailImapTransport
|
||||
from django_mailbox.transports.office365 import Office365Transport
|
||||
6
build/lib/django_mailbox/transports/babyl.py
Normal file
6
build/lib/django_mailbox/transports/babyl.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from mailbox import Babyl
|
||||
from django_mailbox.transports.generic import GenericFileMailbox
|
||||
|
||||
|
||||
class BabylTransport(GenericFileMailbox):
|
||||
_variant = Babyl
|
||||
11
build/lib/django_mailbox/transports/base.py
Normal file
11
build/lib/django_mailbox/transports/base.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import email
|
||||
|
||||
# Do *not* remove this, we need to use this in subclasses of EmailTransport
|
||||
from email.errors import MessageParseError # noqa: F401
|
||||
|
||||
|
||||
class EmailTransport:
|
||||
def get_email_from_bytes(self, contents):
|
||||
message = email.message_from_bytes(contents)
|
||||
|
||||
return message
|
||||
26
build/lib/django_mailbox/transports/generic.py
Normal file
26
build/lib/django_mailbox/transports/generic.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import sys
|
||||
|
||||
from .base import EmailTransport
|
||||
|
||||
|
||||
class GenericFileMailbox(EmailTransport):
|
||||
_variant = None
|
||||
_path = None
|
||||
|
||||
def __init__(self, path):
|
||||
super().__init__()
|
||||
self._path = path
|
||||
|
||||
def get_instance(self):
|
||||
return self._variant(self._path)
|
||||
|
||||
def get_message(self, condition=None):
|
||||
repository = self.get_instance()
|
||||
repository.lock()
|
||||
for key, message in repository.items():
|
||||
if condition and not condition(message):
|
||||
continue
|
||||
repository.remove(key)
|
||||
yield message
|
||||
repository.flush()
|
||||
repository.unlock()
|
||||
57
build/lib/django_mailbox/transports/gmail.py
Normal file
57
build/lib/django_mailbox/transports/gmail.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import logging
|
||||
|
||||
from django_mailbox.transports.imap import ImapTransport
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GmailImapTransport(ImapTransport):
|
||||
|
||||
def connect(self, username, password):
|
||||
# Try to use oauth2 first. It's much safer
|
||||
try:
|
||||
self._connect_oauth(username)
|
||||
except (TypeError, ValueError) as e:
|
||||
logger.warning("Couldn't do oauth2 because %s" % e)
|
||||
self.server = self.transport(self.hostname, self.port)
|
||||
typ, msg = self.server.login(username, password)
|
||||
self.server.select()
|
||||
|
||||
def _connect_oauth(self, username):
|
||||
# username should be an email address that has already been authorized
|
||||
# for gmail access
|
||||
try:
|
||||
from django_mailbox.google_utils import (
|
||||
get_google_access_token,
|
||||
fetch_user_info,
|
||||
AccessTokenNotFound,
|
||||
)
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
"Install python-social-auth to use oauth2 auth for gmail"
|
||||
)
|
||||
|
||||
access_token = None
|
||||
while access_token is None:
|
||||
try:
|
||||
access_token = get_google_access_token(username)
|
||||
google_email_address = fetch_user_info(username)['email']
|
||||
except TypeError:
|
||||
# This means that the google process took too long
|
||||
# Trying again is the right thing to do
|
||||
pass
|
||||
except AccessTokenNotFound:
|
||||
raise ValueError(
|
||||
"No Token available in python-social-auth for %s" % (
|
||||
username
|
||||
)
|
||||
)
|
||||
|
||||
auth_string = 'user={}\1auth=Bearer {}\1\1'.format(
|
||||
google_email_address,
|
||||
access_token
|
||||
)
|
||||
self.server = self.transport(self.hostname, self.port)
|
||||
self.server.authenticate('XOAUTH2', lambda x: auth_string)
|
||||
self.server.select()
|
||||
137
build/lib/django_mailbox/transports/imap.py
Normal file
137
build/lib/django_mailbox/transports/imap.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import imaplib
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from .base import EmailTransport, MessageParseError
|
||||
|
||||
|
||||
# By default, imaplib will raise an exception if it encounters more
|
||||
# than 10k bytes; sometimes users attempt to consume mailboxes that
|
||||
# have a more, and modern computers are skookum-enough to handle just
|
||||
# a *few* more messages without causing any sort of problem.
|
||||
imaplib._MAXLINE = 1000000
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImapTransport(EmailTransport):
|
||||
def __init__(
|
||||
self, hostname, port=None, ssl=False, tls=False,
|
||||
archive='', folder=None,
|
||||
):
|
||||
self.max_message_size = getattr(
|
||||
settings,
|
||||
'DJANGO_MAILBOX_MAX_MESSAGE_SIZE',
|
||||
False
|
||||
)
|
||||
self.integration_testing_subject = getattr(
|
||||
settings,
|
||||
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
|
||||
None
|
||||
)
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.archive = archive
|
||||
self.folder = folder
|
||||
self.tls = tls
|
||||
if ssl:
|
||||
self.transport = imaplib.IMAP4_SSL
|
||||
if not self.port:
|
||||
self.port = 993
|
||||
else:
|
||||
self.transport = imaplib.IMAP4
|
||||
if not self.port:
|
||||
self.port = 143
|
||||
|
||||
def connect(self, username, password):
|
||||
self.server = self.transport(self.hostname, self.port)
|
||||
if self.tls:
|
||||
self.server.starttls()
|
||||
typ, msg = self.server.login(username, password)
|
||||
|
||||
if self.folder:
|
||||
self.server.select(self.folder)
|
||||
else:
|
||||
self.server.select()
|
||||
|
||||
def _get_all_message_ids(self):
|
||||
# Fetch all the message uids
|
||||
response, message_ids = self.server.uid('search', None, 'ALL')
|
||||
message_id_string = message_ids[0].strip()
|
||||
# Usually `message_id_string` will be a list of space-separated
|
||||
# ids; we must make sure that it isn't an empty string before
|
||||
# splitting into individual UIDs.
|
||||
if message_id_string:
|
||||
return message_id_string.decode().split(' ')
|
||||
return []
|
||||
|
||||
def _get_small_message_ids(self, message_ids):
|
||||
# Using existing message uids, get the sizes and
|
||||
# return only those that are under the size
|
||||
# limit
|
||||
safe_message_ids = []
|
||||
|
||||
status, data = self.server.uid(
|
||||
'fetch',
|
||||
','.join(message_ids),
|
||||
'(RFC822.SIZE)'
|
||||
)
|
||||
|
||||
for each_msg in data:
|
||||
each_msg = each_msg.decode()
|
||||
try:
|
||||
uid = each_msg.split(' ')[2]
|
||||
size = each_msg.split(' ')[4].rstrip(')')
|
||||
if int(size) <= int(self.max_message_size):
|
||||
safe_message_ids.append(uid)
|
||||
except ValueError as e:
|
||||
logger.warning(
|
||||
"ValueError: {} working on {}".format(e, each_msg[0])
|
||||
)
|
||||
pass
|
||||
return safe_message_ids
|
||||
|
||||
def get_message(self, condition=None):
|
||||
message_ids = self._get_all_message_ids()
|
||||
|
||||
if not message_ids:
|
||||
return
|
||||
|
||||
# Limit the uids to the small ones if we care about that
|
||||
if self.max_message_size:
|
||||
message_ids = self._get_small_message_ids(message_ids)
|
||||
|
||||
if self.archive:
|
||||
typ, folders = self.server.list(pattern=self.archive)
|
||||
if folders[0] is None:
|
||||
# If the archive folder does not exist, create it
|
||||
self.server.create(self.archive)
|
||||
|
||||
for uid in message_ids:
|
||||
try:
|
||||
typ, msg_contents = self.server.uid('fetch', uid, '(RFC822)')
|
||||
if not msg_contents:
|
||||
continue
|
||||
try:
|
||||
message = self.get_email_from_bytes(msg_contents[0][1])
|
||||
except TypeError:
|
||||
# This happens if another thread/process deletes the
|
||||
# message between our generating the ID list and our
|
||||
# processing it here.
|
||||
continue
|
||||
|
||||
if condition and not condition(message):
|
||||
continue
|
||||
|
||||
yield message
|
||||
except MessageParseError:
|
||||
continue
|
||||
|
||||
if self.archive:
|
||||
self.server.uid('copy', uid, self.archive)
|
||||
|
||||
self.server.uid('store', uid, "+FLAGS", "(\\Deleted)")
|
||||
self.server.expunge()
|
||||
return
|
||||
9
build/lib/django_mailbox/transports/maildir.py
Normal file
9
build/lib/django_mailbox/transports/maildir.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from mailbox import Maildir
|
||||
from django_mailbox.transports.generic import GenericFileMailbox
|
||||
|
||||
|
||||
class MaildirTransport(GenericFileMailbox):
|
||||
_variant = Maildir
|
||||
|
||||
def get_instance(self):
|
||||
return self._variant(self._path, None)
|
||||
6
build/lib/django_mailbox/transports/mbox.py
Normal file
6
build/lib/django_mailbox/transports/mbox.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from mailbox import mbox
|
||||
from django_mailbox.transports.generic import GenericFileMailbox
|
||||
|
||||
|
||||
class MboxTransport(GenericFileMailbox):
|
||||
_variant = mbox
|
||||
6
build/lib/django_mailbox/transports/mh.py
Normal file
6
build/lib/django_mailbox/transports/mh.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from mailbox import MH
|
||||
from django_mailbox.transports.generic import GenericFileMailbox
|
||||
|
||||
|
||||
class MHTransport(GenericFileMailbox):
|
||||
_variant = MH
|
||||
6
build/lib/django_mailbox/transports/mmdf.py
Normal file
6
build/lib/django_mailbox/transports/mmdf.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from mailbox import MMDF
|
||||
from django_mailbox.transports.generic import GenericFileMailbox
|
||||
|
||||
|
||||
class MMDFTransport(GenericFileMailbox):
|
||||
_variant = MMDF
|
||||
131
build/lib/django_mailbox/transports/office365.py
Normal file
131
build/lib/django_mailbox/transports/office365.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import O365
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from .base import EmailTransport, MessageParseError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Office365Transport(EmailTransport):
|
||||
def __init__(
|
||||
self, hostname, port=None, ssl=False, tls=False,
|
||||
archive='', folder=None, client_id=None, client_secret=None, tenant_id=None
|
||||
):
|
||||
self.max_message_size = getattr(
|
||||
settings,
|
||||
'DJANGO_MAILBOX_MAX_MESSAGE_SIZE',
|
||||
False
|
||||
)
|
||||
self.integration_testing_subject = getattr(
|
||||
settings,
|
||||
'DJANGO_MAILBOX_INTEGRATION_TESTING_SUBJECT',
|
||||
None
|
||||
)
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.archive = archive
|
||||
self.folder = folder
|
||||
self.tls = tls
|
||||
if ssl:
|
||||
#self.transport = imaplib.IMAP4_SSL
|
||||
if not self.port:
|
||||
self.port = 993
|
||||
else:
|
||||
#self.transport = imaplib.IMAP4
|
||||
if not self.port:
|
||||
self.port = 143
|
||||
self.client_id = client_id
|
||||
self.client_secret = client_secret
|
||||
self.tenant_id = tenant_id
|
||||
|
||||
def connect(self, username, password):
|
||||
credentials = (self.client_id, self.client_secret)
|
||||
|
||||
# the default protocol will be Microsoft Graph
|
||||
# the default authentication method will be "on behalf of a user"
|
||||
|
||||
self.server = O365.Account(credentials, auth_flow_type='credentials', tenant_id=self.tenant_id)
|
||||
if self.server.authenticate(scopes=['mailbox']):
|
||||
print('Authenticated!')
|
||||
|
||||
def _get_all_message_ids(self):
|
||||
# # Fetch all the message uids
|
||||
# response, message_ids = self.server.uid('search', None, 'ALL')
|
||||
# message_id_string = message_ids[0].strip()
|
||||
# # Usually `message_id_string` will be a list of space-separated
|
||||
# # ids; we must make sure that it isn't an empty string before
|
||||
# # splitting into individual UIDs.
|
||||
# if message_id_string:
|
||||
# return message_id_string.decode().split(' ')
|
||||
# return []
|
||||
|
||||
def _get_small_message_ids(self, message_ids):
|
||||
# Using existing message uids, get the sizes and
|
||||
# return only those that are under the size
|
||||
# limit
|
||||
safe_message_ids = []
|
||||
|
||||
status, data = self.server.uid(
|
||||
'fetch',
|
||||
','.join(message_ids),
|
||||
'(RFC822.SIZE)'
|
||||
)
|
||||
|
||||
for each_msg in data:
|
||||
each_msg = each_msg.decode()
|
||||
try:
|
||||
uid = each_msg.split(' ')[2]
|
||||
size = each_msg.split(' ')[4].rstrip(')')
|
||||
if int(size) <= int(self.max_message_size):
|
||||
safe_message_ids.append(uid)
|
||||
except ValueError as e:
|
||||
logger.warning(
|
||||
"ValueError: {} working on {}".format(e, each_msg[0])
|
||||
)
|
||||
pass
|
||||
return safe_message_ids
|
||||
|
||||
def get_message(self, condition=None):
|
||||
message_ids = self._get_all_message_ids()
|
||||
|
||||
if not message_ids:
|
||||
return
|
||||
|
||||
# Limit the uids to the small ones if we care about that
|
||||
if self.max_message_size:
|
||||
message_ids = self._get_small_message_ids(message_ids)
|
||||
|
||||
if self.archive:
|
||||
typ, folders = self.server.list(pattern=self.archive)
|
||||
if folders[0] is None:
|
||||
# If the archive folder does not exist, create it
|
||||
self.server.create(self.archive)
|
||||
|
||||
for uid in message_ids:
|
||||
try:
|
||||
typ, msg_contents = self.server.uid('fetch', uid, '(RFC822)')
|
||||
if not msg_contents:
|
||||
continue
|
||||
try:
|
||||
message = self.get_email_from_bytes(msg_contents[0][1])
|
||||
except TypeError:
|
||||
# This happens if another thread/process deletes the
|
||||
# message between our generating the ID list and our
|
||||
# processing it here.
|
||||
continue
|
||||
|
||||
if condition and not condition(message):
|
||||
continue
|
||||
|
||||
yield message
|
||||
except MessageParseError:
|
||||
continue
|
||||
|
||||
if self.archive:
|
||||
self.server.uid('copy', uid, self.archive)
|
||||
|
||||
self.server.uid('store', uid, "+FLAGS", "(\\Deleted)")
|
||||
self.server.expunge()
|
||||
return
|
||||
44
build/lib/django_mailbox/transports/pop3.py
Normal file
44
build/lib/django_mailbox/transports/pop3.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
from poplib import POP3, POP3_SSL
|
||||
|
||||
from .base import EmailTransport, MessageParseError
|
||||
|
||||
|
||||
class Pop3Transport(EmailTransport):
|
||||
def __init__(self, hostname, port=None, ssl=False):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
if ssl:
|
||||
self.transport = POP3_SSL
|
||||
if not self.port:
|
||||
self.port = 995
|
||||
else:
|
||||
self.transport = POP3
|
||||
if not self.port:
|
||||
self.port = 110
|
||||
|
||||
def connect(self, username, password):
|
||||
self.server = self.transport(self.hostname, self.port)
|
||||
self.server.user(username)
|
||||
self.server.pass_(password)
|
||||
|
||||
def get_message_body(self, message_lines):
|
||||
return bytes('\r\n', 'ascii').join(message_lines)
|
||||
|
||||
def get_message(self, condition=None):
|
||||
message_count = len(self.server.list()[1])
|
||||
for i in range(message_count):
|
||||
try:
|
||||
msg_contents = self.get_message_body(
|
||||
self.server.retr(i + 1)[1]
|
||||
)
|
||||
message = self.get_email_from_bytes(msg_contents)
|
||||
|
||||
if condition and not condition(message):
|
||||
continue
|
||||
|
||||
yield message
|
||||
except MessageParseError:
|
||||
continue
|
||||
self.server.dele(i + 1)
|
||||
self.server.quit()
|
||||
return
|
||||
Loading…
Add table
Add a link
Reference in a new issue