aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuillaume Horel <guillaume.horel@gmail.com>2017-07-23 15:18:41 -0400
committerGuillaume Horel <guillaume.horel@gmail.com>2017-07-23 15:36:29 -0400
commit26b653dbb9fda6f70ae1caac812c151c5f706bb1 (patch)
treeb2d3617a22b57e9be4119593dc537c0249c33c9c
parent6c9c39b6856f8c7540944413bcdc47d02affa237 (diff)
downloadfamille-flask-26b653dbb9fda6f70ae1caac812c151c5f706bb1.tar.gz
handle comments through email responsesemail
-rw-r--r--email_helpers.py141
-rw-r--r--famille.py24
2 files changed, 153 insertions, 12 deletions
diff --git a/email_helpers.py b/email_helpers.py
new file mode 100644
index 0000000..15d69a2
--- /dev/null
+++ b/email_helpers.py
@@ -0,0 +1,141 @@
+from apiclient.discovery import build
+from apiclient import errors
+from httplib2 import Http
+import oauth2client
+from oauth2client import client, tools
+import os
+import json
+import base64
+from email.message import EmailMessage
+import email
+from email.utils import parseaddr, parsedate_to_datetime
+import argparse
+from bs4 import BeautifulSoup
+
+SCOPES = 'https://www.googleapis.com/auth/gmail.modify'
+CLIENT_SECRET_FILE = os.path.expanduser('~/client_id.json')
+APPLICATION_NAME = 'Famille'
+
+def get_gmail_service():
+ """Gets valid user credentials from storage.
+
+ If nothing has been stored, or if the stored credentials are invalid,
+ the OAuth2 flow is completed to obtain the new credentials.
+
+ Returns:
+ Credentials, the obtained credential.
+ """
+ credential_dir = os.path.expanduser('~/.credentials')
+ if not os.path.exists(credential_dir):
+ os.makedirs(credential_dir)
+ credential_path = os.path.join(credential_dir, 'news.horel@gmail.com')
+ store = oauth2client.file.Storage(credential_path)
+ credentials = store.get()
+ if not credentials or credentials.invalid:
+ flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
+ flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
+ flow.user_agent = APPLICATION_NAME
+ credentials = tools.run_flow(flow, store, flags)
+ print('Storing credentials to ' + credential_path)
+ service = build('gmail', 'v1', http=credentials.authorize(Http()))
+ return service
+
+class GmailMessage(EmailMessage):
+ _service = get_gmail_service()
+
+ def msgdict(self):
+ return {'raw': base64.urlsafe_b64encode(self.as_bytes()).decode()}
+
+ def send(self):
+ try:
+ message = (self._service.users().messages().
+ send(userId='me',body=self.msgdict())
+ .execute())
+ print('Message Id: %s' % message['id'])
+ except errors.HttpError as error:
+ print('An error occurred: %s' % error)
+
+ @classmethod
+ def from_id(cls, msg_id, user_id='me'):
+ try:
+ message = (cls._service.users().messages().
+ get(userId=user_id, id=msg_id, format='raw').execute())
+ return email.message_from_bytes(
+ base64.urlsafe_b64decode(message['raw']),
+ policy=email.policy.EmailPolicy())
+ except errors.HttpError as error:
+ print(json.loads(error.content.decode('utf-8'))['error']['message'])
+
+def ListMessagesWithLabels(service, user_id, label_ids=[]):
+ """List all Messages of the user's mailbox with label_ids applied.
+
+ Args:
+ service: Authorized Gmail API service instance.
+ user_id: User's email address. The special value "me"
+ can be used to indicate the authenticated user.
+ label_ids: Only return Messages with these labelIds applied.
+
+ Returns:
+ List of Messages that have all required Labels applied. Note that the
+ returned list contains Message IDs, you must use get with the
+ appropriate id to get the details of a Message.
+ """
+ try:
+ response = service.users().messages().list(userId=user_id,
+ labelIds=label_ids).execute()
+ messages = []
+ if 'messages' in response:
+ messages.extend(response['messages'])
+ while 'nextPageToken' in response:
+ page_token = response['nextPageToken']
+ response = service.users().messages().list(userId=user_id,
+ labelIds=label_ids,
+ pageToken=page_token).execute()
+ messages.extend(response['messages'])
+
+ return messages
+ except errors.HttpError as error:
+ print(json.loads(error.content.decode('utf-8'))['error']['message'])
+
+def labels_dict(service, user_id):
+ """Returns a dictionary mapping labels to labelids.
+
+ Args:
+ service: Authorized Gmail API service instance.
+ user_id: User's email address. The special value "me"
+
+ Returns:
+ dictionary mapping labels to labelids.
+ """
+ try:
+ response = service.users().labels().list(userId=user_id).execute()
+ labels = response['labels']
+ return {label['name']: label['id'] for label in labels}
+ except errors.HttpError as error:
+ print(json.loads(error.content.decode('utf-8'))['error']['message'])
+
+def extract_response(content):
+ soup = BeautifulSoup(content, 'lxml')
+ reply = soup.find('div', dir='ltr')
+ return reply
+
+if __name__ == "__main__":
+ import re
+ regex = re.compile("[^+]*\+([^@]*)")
+ for msg_id in ListMessagesWithLabels(GmailMessage._service, 'me', 'INBOX'):
+ mail = GmailMessage.from_id(msg_id['id'])
+ _, email_addr = parseaddr(mail['From'])
+ m = regex.match(mail['To'])
+ if m:
+ body = mail.get_body()
+ if body.get_content_type() == 'text/html':
+ comment_cache = extract_response(body.get_content())
+ comment = comment_cache.get_text()
+ elif body.get_content_type() == 'text/plain':
+ comment = comment_cache = body.get_content()
+ news_id = m.groups()[0]
+ date = parsedate_to_datetime(mail['Date'])
+ sql_str = "INSERT INTO comments(date, user_id, content, news_id, content_cache) "
+ "VALUES(?, ?, ?, ?)"
+ db.execute(sql_str, (date, user_id, comment.get_text(), news_id, comment)
+ print(msg_id['id'], news_id, email_addr, date, comment)
diff --git a/famille.py b/famille.py
index bc44f4c..9d9894d 100644
--- a/famille.py
+++ b/famille.py
@@ -11,10 +11,9 @@ from datetime import datetime
import time
from email import utils
from bs4 import BeautifulSoup
-from flask_mail import Mail, Message
import locale
from smartypants import smartypants, Attr
-
+from email_helpers import GmailEmailMessage
locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8')
@@ -27,7 +26,6 @@ def rstify(string):
# configuration
app = Flask(__name__)
app.config.from_envvar('CONF')
-mail = Mail(app)
@app.template_filter('shortify')
def shortify(string):
@@ -97,7 +95,6 @@ def get_db():
g.timezone = pytz.common_timezones
return db
-
@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
@@ -152,15 +149,18 @@ def add_news():
emails = query_db("SELECT email from users where notify=1")
emails = [email["email"] for email in emails if email["email"]]
if emails:
- message = Message(request.form['title'],
- sender="news.horel@gmail.com")
- message.html = content_cache
+ message = GmailEmailMessage()
+ message['to'] = emails
+ message['subject'] = request.form['title']
+ message['reply-to'] = "news.horel+{}@gmail.com".format(news_id)
+ message.set_content(content)
url = url_for('show_news', news_id=news_id, _external=True)
- message.html += ("<p style='margin-top:2em'>Vous pouvez "
- "<a href='{0}'>Lire cette nouvelle</a> "
- "sur le site de la famille.</p>").format(url)
- message.recipients = emails
- mail.send(message)
+ html_content = content_cache +
+ ("<p style='margin-top:2em'>Vous pouvez "
+ "<a href='{0}'>Lire cette nouvelle</a> "
+ "sur le site de la famille.</p>").format(url)
+ message.add_alternative(html_content, subtype='html')
+ message.send()
return redirect(url_for('show_news', news_id=news_id))
else: