summaryrefslogtreecommitdiff
path: root/email_assistant
diff options
context:
space:
mode:
authorJames E. Blair <corvus@gnu.org>2019-06-09 10:05:11 -0700
committerJames E. Blair <corvus@gnu.org>2019-06-09 10:07:14 -0700
commit3177ba26421b730bdf475fc2e991eef9ab9ef067 (patch)
treea7cce1756ccb5d6c50b1b56e1b24885ba683851f /email_assistant
parentc44874cfa48bf00b83057033fc7800b35e157702 (diff)
Add support for Delta
Also add an undocumented mailbox driver for a directory of files for ease of testing (this could probably become a maildir driver with a bit more work). Remove unecessary decode calls from the message traversal.
Diffstat (limited to 'email_assistant')
-rw-r--r--email_assistant/assistant.py36
-rw-r--r--email_assistant/plugins/__init__.py2
-rw-r--r--email_assistant/plugins/delta.py115
-rw-r--r--email_assistant/plugins/eventbrite.py2
-rw-r--r--email_assistant/plugins/general.py2
-rw-r--r--email_assistant/plugins/marriott.py2
-rw-r--r--email_assistant/plugins/united.py2
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:
38IMAP_BACKFILL = 180 38IMAP_BACKFILL = 180
39 39
40class Mailbox: 40class 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
88class 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
88class Calendar: 103class 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
117class Assistant: 132class 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 @@
21from . import eventbrite 21from . import eventbrite
22from . import marriott 22from . import marriott
23from . import united 23from . import united
24from . import delta
24 25
25plugins = [ 26plugins = [
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
21import re
22import logging
23import hashlib
24
25from bs4 import BeautifulSoup
26import dateutil.parser
27import dateutil.tz
28import inscriptis
29import vobject
30
31from email_assistant import iata
32from email_assistant import plugin
33
34def 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
43class 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