diff options
Diffstat (limited to 'email_assistant')
| -rw-r--r-- | email_assistant/assistant.py | 36 | ||||
| -rw-r--r-- | email_assistant/plugins/__init__.py | 2 | ||||
| -rw-r--r-- | email_assistant/plugins/delta.py | 115 | ||||
| -rw-r--r-- | email_assistant/plugins/eventbrite.py | 2 | ||||
| -rw-r--r-- | email_assistant/plugins/general.py | 2 | ||||
| -rw-r--r-- | email_assistant/plugins/marriott.py | 2 | ||||
| -rw-r--r-- | email_assistant/plugins/united.py | 2 |
7 files changed, 149 insertions, 12 deletions
diff --git a/email_assistant/assistant.py b/email_assistant/assistant.py index a784077..d39b016 100644 --- a/email_assistant/assistant.py +++ b/email_assistant/assistant.py | |||
| @@ -37,7 +37,7 @@ from email_assistant import plugins | |||
| 37 | # Number of days to look backwards when scanning a mailbox for the first time: | 37 | # Number of days to look backwards when scanning a mailbox for the first time: |
| 38 | IMAP_BACKFILL = 180 | 38 | IMAP_BACKFILL = 180 |
| 39 | 39 | ||
| 40 | class Mailbox: | 40 | class IMAPMailbox: |
| 41 | def __init__(self, name, host, username, password, folders): | 41 | def __init__(self, name, host, username, password, folders): |
| 42 | self.log = logging.getLogger('assistant.mailbox') | 42 | self.log = logging.getLogger('assistant.mailbox') |
| 43 | self.name = name | 43 | self.name = name |
| @@ -85,6 +85,21 @@ class Mailbox: | |||
| 85 | with open(self.state_file, 'w') as f: | 85 | with open(self.state_file, 'w') as f: |
| 86 | json.dump(self.uidinfo, f) | 86 | json.dump(self.uidinfo, f) |
| 87 | 87 | ||
| 88 | class DirMailbox: | ||
| 89 | def __init__(self, name, directory): | ||
| 90 | self.log = logging.getLogger('assistant.mailbox') | ||
| 91 | self.name = name | ||
| 92 | self.directory = directory | ||
| 93 | |||
| 94 | def get_messages(self): | ||
| 95 | for fn in os.listdir(self.directory): | ||
| 96 | with open(os.path.join(self.directory, fn), 'rb') as f: | ||
| 97 | msg = f.read() | ||
| 98 | yield msg | ||
| 99 | |||
| 100 | def save(self): | ||
| 101 | pass | ||
| 102 | |||
| 88 | class Calendar: | 103 | class Calendar: |
| 89 | def __init__(self, url, username, password, calendar): | 104 | def __init__(self, url, username, password, calendar): |
| 90 | self.log = logging.getLogger('assistant.calendar') | 105 | self.log = logging.getLogger('assistant.calendar') |
| @@ -116,7 +131,7 @@ class Calendar: | |||
| 116 | 131 | ||
| 117 | class Assistant: | 132 | class Assistant: |
| 118 | def __init__(self): | 133 | def __init__(self): |
| 119 | self.log = logging.getLogger('main') | 134 | self.log = logging.getLogger('assistant.main') |
| 120 | self.geolocator = None | 135 | self.geolocator = None |
| 121 | self.tzfinder = None | 136 | self.tzfinder = None |
| 122 | self.plugins = [] | 137 | self.plugins = [] |
| @@ -151,12 +166,17 @@ class Assistant: | |||
| 151 | for section in config.sections(): | 166 | for section in config.sections(): |
| 152 | if section.startswith('mailbox '): | 167 | if section.startswith('mailbox '): |
| 153 | name = section.split()[1] | 168 | name = section.split()[1] |
| 154 | mailboxes[name] = Mailbox( | 169 | if config[section]['type'].lower() == 'imap': |
| 155 | name, | 170 | mailboxes[name] = IMAPMailbox( |
| 156 | config[section]['host'], | 171 | name, |
| 157 | config[section]['username'], | 172 | config[section]['host'], |
| 158 | config[section]['password'], | 173 | config[section]['username'], |
| 159 | config[section]['folders'].split(',')) | 174 | config[section]['password'], |
| 175 | config[section]['folders'].split(',')) | ||
| 176 | elif config[section]['type'].lower() == 'dir': | ||
| 177 | mailboxes[name] = DirMailbox( | ||
| 178 | name, | ||
| 179 | config[section]['path']) | ||
| 160 | elif section.startswith('calendar '): | 180 | elif section.startswith('calendar '): |
| 161 | name = section.split()[1] | 181 | name = section.split()[1] |
| 162 | calendars[name] = Calendar( | 182 | calendars[name] = Calendar( |
diff --git a/email_assistant/plugins/__init__.py b/email_assistant/plugins/__init__.py index 10b28f0..295852e 100644 --- a/email_assistant/plugins/__init__.py +++ b/email_assistant/plugins/__init__.py | |||
| @@ -21,9 +21,11 @@ | |||
| 21 | from . import eventbrite | 21 | from . import eventbrite |
| 22 | from . import marriott | 22 | from . import marriott |
| 23 | from . import united | 23 | from . import united |
| 24 | from . import delta | ||
| 24 | 25 | ||
| 25 | plugins = [ | 26 | plugins = [ |
| 26 | eventbrite.Plugin, | 27 | eventbrite.Plugin, |
| 27 | marriott.Plugin, | 28 | marriott.Plugin, |
| 28 | united.Plugin, | 29 | united.Plugin, |
| 30 | delta.Plugin, | ||
| 29 | ] | 31 | ] |
diff --git a/email_assistant/plugins/delta.py b/email_assistant/plugins/delta.py new file mode 100644 index 0000000..50da506 --- /dev/null +++ b/email_assistant/plugins/delta.py | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | #!/usr/bin/env python3 | ||
| 2 | |||
| 3 | # Copyright (C) 2019 James E. Blair <corvus@gnu.org> | ||
| 4 | # | ||
| 5 | # This file is part of Email-assistant. | ||
| 6 | # | ||
| 7 | # Email-assistant is free software: you can redistribute it and/or | ||
| 8 | # modify it under the terms of the GNU Affero General Public License | ||
| 9 | # as published by the Free Software Foundation, either version 3 of | ||
| 10 | # the License, or (at your option) any later version. | ||
| 11 | # | ||
| 12 | # Email-assistant is distributed in the hope that it will be useful, | ||
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | # General Public License for more details. | ||
| 16 | # | ||
| 17 | # You should have received a copy of the GNU General Public License | ||
| 18 | # along with Email-assistant. If not, see | ||
| 19 | # <https://www.gnu.org/licenses/>. | ||
| 20 | |||
| 21 | import re | ||
| 22 | import logging | ||
| 23 | import hashlib | ||
| 24 | |||
| 25 | from bs4 import BeautifulSoup | ||
| 26 | import dateutil.parser | ||
| 27 | import dateutil.tz | ||
| 28 | import inscriptis | ||
| 29 | import vobject | ||
| 30 | |||
| 31 | from email_assistant import iata | ||
| 32 | from email_assistant import plugin | ||
| 33 | |||
| 34 | def parse_dep_arr(dep_date, flight_date, flight_time, code): | ||
| 35 | flight_year = dep_date.year | ||
| 36 | tz = iata.tzmap[code] | ||
| 37 | flight_time = dateutil.parser.parse(flight_date + ' ' + flight_time) | ||
| 38 | if flight_time.year < flight_year: | ||
| 39 | flight_time.replace(year=flight_year) | ||
| 40 | flight_time = flight_time.replace(tzinfo=dateutil.tz.gettz(tz)) | ||
| 41 | return flight_time | ||
| 42 | |||
| 43 | class Plugin(plugin.Plugin): | ||
| 44 | name = 'delta' | ||
| 45 | |||
| 46 | def match(self, msg): | ||
| 47 | if ('DeltaAirLines@e.delta.com' in msg['From'] and | ||
| 48 | 'Your Flight Receipt' in msg['Subject']): | ||
| 49 | return True | ||
| 50 | |||
| 51 | def get_events(self, msg): | ||
| 52 | events = [] | ||
| 53 | for part in msg.walk(): | ||
| 54 | if part.get_content_type() == 'text/html': | ||
| 55 | soup = BeautifulSoup(part.get_payload(decode=True), 'html.parser') | ||
| 56 | flights = [] | ||
| 57 | flight = None | ||
| 58 | dep_date = None | ||
| 59 | start = soup.find(string=re.compile('FLIGHT INFO STARTS')) | ||
| 60 | for row in start.parent.find_all('tr', recursive=False): | ||
| 61 | row = [x.strip() for x in row.strings if x.strip()] | ||
| 62 | if len(row) == 3 and row[1:] == ['DEPART', 'ARRIVE']: | ||
| 63 | dep_date = row[0] | ||
| 64 | continue | ||
| 65 | if dep_date is None: continue | ||
| 66 | f = { | ||
| 67 | 'dep_date': dep_date, | ||
| 68 | #'num': row[0], | ||
| 69 | #'cabin': row[1], | ||
| 70 | 'dep_city': row[2], | ||
| 71 | 'dep_time': row[3], | ||
| 72 | 'arr_city': row[4], | ||
| 73 | 'arr_time': row[5]} | ||
| 74 | if len(row) > 6: | ||
| 75 | f['arr_date'] = row[6].replace('*', '').strip() | ||
| 76 | else: | ||
| 77 | f['arr_date'] = dep_date | ||
| 78 | flights.append(f) | ||
| 79 | start = soup.find(string=re.compile('Checked Bag Allowance')) | ||
| 80 | while start.name != 'table': start = start.parent | ||
| 81 | start = start.parent | ||
| 82 | while start.name != 'table': start = start.parent | ||
| 83 | index = 0 | ||
| 84 | for row in start.find_all('tr', recursive=False): | ||
| 85 | row = [x.strip() for x in row.strings if x.strip()] | ||
| 86 | if len(row) != 3: continue | ||
| 87 | try: | ||
| 88 | dep_date = dateutil.parser.parse(row[0]) | ||
| 89 | except Exception: | ||
| 90 | continue | ||
| 91 | f = flights[index] | ||
| 92 | f['dep_code'] = row[1].split()[-1].strip() | ||
| 93 | f['arr_code'] = row[2].split()[-1].strip() | ||
| 94 | f['dep_dt'] = parse_dep_arr( | ||
| 95 | dep_date, f['dep_date'], f['dep_time'], f['dep_code']) | ||
| 96 | f['arr_dt'] = parse_dep_arr( | ||
| 97 | dep_date, f['arr_date'], f['arr_time'], f['arr_code']) | ||
| 98 | index += 1 | ||
| 99 | for f in flights: | ||
| 100 | self.log.debug('%s %s %s %s', | ||
| 101 | f['dep_dt'], f['arr_dt'], f['dep_code'], f['arr_code']) | ||
| 102 | cal = vobject.iCalendar() | ||
| 103 | event = cal.add('vevent') | ||
| 104 | event.add('dtstart').value = f['dep_dt'] | ||
| 105 | event.add('dtend').value = f['arr_dt'] | ||
| 106 | summary = "Flight from %s to %s" % (f['dep_code'], f['arr_code']) | ||
| 107 | event.add('summary').value = summary | ||
| 108 | text = inscriptis.get_text(str(soup)) | ||
| 109 | text = re.sub(r'([^ ]+)\s*\n', '\\1\n', text) | ||
| 110 | event.add('description').value = text | ||
| 111 | event.add('location').value = "%s airport" % (f['dep_code'],) | ||
| 112 | uid = hashlib.sha1((str(f['dep_time']) + summary).encode('utf8')).hexdigest() | ||
| 113 | event.add('uid').value = uid | ||
| 114 | events.append(cal) | ||
| 115 | return events | ||
diff --git a/email_assistant/plugins/eventbrite.py b/email_assistant/plugins/eventbrite.py index 9ef073a..afd869f 100644 --- a/email_assistant/plugins/eventbrite.py +++ b/email_assistant/plugins/eventbrite.py | |||
| @@ -44,7 +44,7 @@ class Plugin(plugin.Plugin): | |||
| 44 | events = [] | 44 | events = [] |
| 45 | for part in msg.walk(): | 45 | for part in msg.walk(): |
| 46 | if part.get_content_type() == 'text/html': | 46 | if part.get_content_type() == 'text/html': |
| 47 | soup = BeautifulSoup(part.get_payload(decode=True).decode('utf8'), 'html.parser') | 47 | soup = BeautifulSoup(part.get_payload(decode=True), 'html.parser') |
| 48 | data = json.loads(soup.find('script', type="application/ld+json").string) | 48 | data = json.loads(soup.find('script', type="application/ld+json").string) |
| 49 | 49 | ||
| 50 | address = data['reservationFor']['location']['address'] | 50 | address = data['reservationFor']['location']['address'] |
diff --git a/email_assistant/plugins/general.py b/email_assistant/plugins/general.py index c0517b8..e21274d 100644 --- a/email_assistant/plugins/general.py +++ b/email_assistant/plugins/general.py | |||
| @@ -24,7 +24,7 @@ def get_events(msg): | |||
| 24 | log = logging.getLogger('assistant.marriott') | 24 | log = logging.getLogger('assistant.marriott') |
| 25 | for part in msg.walk(): | 25 | for part in msg.walk(): |
| 26 | if part.get_content_type() == 'text/html': | 26 | if part.get_content_type() == 'text/html': |
| 27 | soup = BeautifulSoup(part.get_payload(decode=True).decode('utf8'), 'html.parser') | 27 | soup = BeautifulSoup(part.get_payload(decode=True), 'html.parser') |
| 28 | 28 | ||
| 29 | start = end = location = None | 29 | start = end = location = None |
| 30 | for element in soup.descendants: | 30 | for element in soup.descendants: |
diff --git a/email_assistant/plugins/marriott.py b/email_assistant/plugins/marriott.py index 52597cd..1f2abab 100644 --- a/email_assistant/plugins/marriott.py +++ b/email_assistant/plugins/marriott.py | |||
| @@ -43,7 +43,7 @@ class Plugin(plugin.Plugin): | |||
| 43 | events = [] | 43 | events = [] |
| 44 | for part in msg.walk(): | 44 | for part in msg.walk(): |
| 45 | if part.get_content_type() == 'text/html': | 45 | if part.get_content_type() == 'text/html': |
| 46 | soup = BeautifulSoup(part.get_payload(decode=True).decode('utf8'), 'html.parser') | 46 | soup = BeautifulSoup(part.get_payload(decode=True), 'html.parser') |
| 47 | summary = soup.find_all('table')[7].a.string.strip() | 47 | summary = soup.find_all('table')[7].a.string.strip() |
| 48 | location = soup.find_all('table')[9].a.string.strip() | 48 | location = soup.find_all('table')[9].a.string.strip() |
| 49 | start = (soup.find('th', string=re.compile('Check-In:')). | 49 | start = (soup.find('th', string=re.compile('Check-In:')). |
diff --git a/email_assistant/plugins/united.py b/email_assistant/plugins/united.py index 0b33cd9..38de3e2 100644 --- a/email_assistant/plugins/united.py +++ b/email_assistant/plugins/united.py | |||
| @@ -60,7 +60,7 @@ class Plugin(plugin.Plugin): | |||
| 60 | events = [] | 60 | events = [] |
| 61 | for part in msg.walk(): | 61 | for part in msg.walk(): |
| 62 | if part.get_content_type() == 'text/html': | 62 | if part.get_content_type() == 'text/html': |
| 63 | soup = BeautifulSoup(part.get_payload(decode=True).decode('utf8'), 'html.parser') | 63 | soup = BeautifulSoup(part.get_payload(decode=True), 'html.parser') |
| 64 | # confirmation_number = soup.find(class_="eTicketConfirmation").string | 64 | # confirmation_number = soup.find(class_="eTicketConfirmation").string |
| 65 | 65 | ||
| 66 | index = 0 | 66 | index = 0 |
