summaryrefslogtreecommitdiff
path: root/inboxapp-service/app
diff options
context:
space:
mode:
Diffstat (limited to 'inboxapp-service/app')
-rw-r--r--inboxapp-service/app/factory/__init__.py25
-rw-r--r--inboxapp-service/app/inboxapp/__init__.py3
-rw-r--r--inboxapp-service/app/inboxapp/client.py100
-rw-r--r--inboxapp-service/app/inboxapp/mailconverter.py84
-rw-r--r--inboxapp-service/app/search/__init__.py44
-rw-r--r--inboxapp-service/app/smailback.py127
6 files changed, 383 insertions, 0 deletions
diff --git a/inboxapp-service/app/factory/__init__.py b/inboxapp-service/app/factory/__init__.py
new file mode 100644
index 00000000..c7f3cf32
--- /dev/null
+++ b/inboxapp-service/app/factory/__init__.py
@@ -0,0 +1,25 @@
+import inboxapp
+
+class ProviderNotFoundException(Exception):
+ def __init__(self, provider):
+ self.provider = provider
+
+ def __str__(self):
+ return "Provider '%s' not found" % self.provider
+
+class ClientFactory:
+
+ @staticmethod
+ def create(provider, account):
+ if provider == 'inboxapp':
+ return inboxapp.Client(account)
+ raise ProviderNotFoundException(provider)
+
+class MailConverterFactory:
+
+ @staticmethod
+ def create(provider, client):
+ if provider == 'inboxapp':
+ return inboxapp.MailConverter(client)
+ raise ProviderNotFoundException(provider)
+
diff --git a/inboxapp-service/app/inboxapp/__init__.py b/inboxapp-service/app/inboxapp/__init__.py
new file mode 100644
index 00000000..b836e508
--- /dev/null
+++ b/inboxapp-service/app/inboxapp/__init__.py
@@ -0,0 +1,3 @@
+from client import Client
+from mailconverter import MailConverter
+
diff --git a/inboxapp-service/app/inboxapp/client.py b/inboxapp-service/app/inboxapp/client.py
new file mode 100644
index 00000000..cf96a498
--- /dev/null
+++ b/inboxapp-service/app/inboxapp/client.py
@@ -0,0 +1,100 @@
+import json
+import urllib2
+import requests
+
+
+class Client:
+
+ INBOX_APP_ROOT = 'http://localhost:5555/n'
+
+ def _get_user(self, account):
+ accounts = json.load(urllib2.urlopen(self.INBOX_APP_ROOT))
+ return [a for a in accounts if a['email_address'] == account][0]
+
+ def __init__(self, account):
+ self.user = self._get_user(account)
+ self.namespace = self.user['namespace']
+
+ def _get(self, append_url):
+ url = "%s/%s/%s" % (self.INBOX_APP_ROOT, self.namespace, append_url)
+ return requests.get(url).json()
+
+ def _post(self, append_url, body):
+ url = "%s/%s/%s" % (self.INBOX_APP_ROOT, self.namespace, append_url)
+ return requests.post(url, json.dumps(body)).json()
+
+ def _put(self, append_url, body):
+ url = "%s/%s/%s" % (self.INBOX_APP_ROOT, self.namespace, append_url)
+ return requests.put(url, json.dumps(body)).json()
+
+ def mails(self, query):
+ url = "messages"
+ if('tags' in query and len(query['tags']) > 0):
+ url = url + "?tag=%s" % ",".join(query['tags'])
+ return self._get(url)
+
+ def drafts(self):
+ return self._get("drafts")
+
+ def mail(self, mail_id):
+ return self._get("messages/%s" % mail_id)
+
+ def thread(self, thread_id):
+ return self._get("threads/%s" % thread_id)
+
+ def mark_as_read(self, mail_id):
+ mail_to_mark = self.mail(mail_id)
+ self._put("messages/%s" % mail_id, {"unread": False})
+ self.remove_tag_from_thread(mail_to_mark["thread"], "unread")
+
+ def tags_for_thread(self, thread):
+ url = "threads/%s" % thread
+ tags = self._get(url)['tags']
+ return [tag['name'] for tag in tags]
+
+ def add_tag_to_thread(self, thread_id, tag):
+ url = "threads/%s" % thread_id
+ response = self._put(url, {'add_tags': [tag]})
+ return response
+
+ def remove_tag_from_thread(self, thread_id, tag):
+ url = "threads/%s" % thread_id
+ response = self._put(url, {'remove_tags': [tag]})
+ return response
+
+ def delete_mail(self, mail_id):
+ thread_id = self.mail(mail_id)['thread']
+ tags = self.tags_for_thread(thread_id)
+ if('trash' in tags):
+ self.add_tag_to_thread(thread_id, 'delete')
+ else:
+ self.add_tag_to_thread(thread_id, 'trash')
+ return None
+
+ def save_draft(self, draft):
+ if 'id' in draft and draft['id']:
+ url = 'drafts/%s' % draft["id"]
+ else:
+ url = "drafts"
+ result = self._post(url, draft)
+ self.mark_as_read(result['id'])
+ return result['id']
+
+ def send_draft(self, draft):
+ new_draft_id = self.save_draft(draft)
+ response = self._post("send", {"draft_id": new_draft_id})
+ return response
+
+ def draft_reply_for(self, mail_id):
+ thread = self.thread(self.mail(mail_id)["thread"])
+ if thread['drafts']:
+ response = self.mail(thread['drafts'][0])
+ else:
+ response = None
+ return response
+
+ def all_tags(self):
+ return self._get("tags")
+
+ def all_contacts(self, query):
+ return self._get("contacts?filter=%s&order_by=rank" % query['general'])
diff --git a/inboxapp-service/app/inboxapp/mailconverter.py b/inboxapp-service/app/inboxapp/mailconverter.py
new file mode 100644
index 00000000..7841a538
--- /dev/null
+++ b/inboxapp-service/app/inboxapp/mailconverter.py
@@ -0,0 +1,84 @@
+from inboxapp import Client
+from datetime import datetime
+import calendar
+
+
+class MailConverter:
+
+ def __init__(self, client):
+ self.client = client
+
+ def _from_epoch(self, epoch):
+ return datetime.fromtimestamp(epoch).isoformat()
+
+ def _to_epoch(self, iso8601):
+ return calendar.timegm(
+ datetime.strptime(iso8601, "%Y-%m-%dT%H:%M:%S.%f").timetuple()
+ )
+
+ def _to_contacts(self, pixelated_contacts):
+ return [{"name": "", "email": x} for x in pixelated_contacts]
+
+ def _from_contacts(self, inbox_contacts):
+ return [contact['email'] for contact in inbox_contacts]
+
+ def from_mail(self, inbox_mail):
+ tags = sorted(self.client.tags_for_thread(inbox_mail['thread']))
+ status = [] if "unread" in tags else ["read"]
+ return {
+ 'header': {
+ 'from': inbox_mail['from'][0]['email'],
+ 'to': self._from_contacts(inbox_mail['to']),
+ 'cc': self._from_contacts(inbox_mail['cc']),
+ 'bcc': self._from_contacts(inbox_mail['bcc']),
+ 'date': self._from_epoch(inbox_mail['date']),
+ 'subject': inbox_mail['subject']
+ },
+ 'ident': inbox_mail['id'],
+ 'tags': tags,
+ 'status': status,
+ 'security_casing': {},
+ 'body': inbox_mail['body'],
+ }
+
+ def to_mail(self, pixelated_mail, account):
+ mail = {
+ "to": self._to_contacts(pixelated_mail['header']['to']),
+ "cc": self._to_contacts(pixelated_mail['header']['cc']),
+ "bcc": self._to_contacts(pixelated_mail['header']['bcc']),
+ "from": account,
+ "body": pixelated_mail["body"],
+ "subject": pixelated_mail["header"]["subject"],
+ "date": self._to_epoch(datetime.now().isoformat()),
+ "id": pixelated_mail["ident"],
+ "object": "message",
+ }
+ if "draft_reply_for" in pixelated_mail:
+ referred_mail = self.client.mail(pixelated_mail["draft_reply_for"])
+ mail["reply_to_thread"] = referred_mail["thread"]
+ return mail
+
+ def from_tag(self, inbox_tag):
+ default_tags = ["inbox", "sent", "trash", "drafts"]
+ return {
+ 'name': inbox_tag['name'],
+ 'ident': inbox_tag['id'],
+ 'default': inbox_tag['name'] in default_tags,
+ 'counts': {
+ 'total': 0,
+ 'read': 0,
+ 'starred': 0,
+ 'reply': 0
+ }
+ }
+
+ def from_contact(self, inbox_contact):
+ return {
+ 'ident': inbox_contact['id'],
+ 'name': inbox_contact['name'],
+ 'addresses': [inbox_contact['email']],
+ 'mails_received': 0,
+ 'mails_sent': 0,
+ 'last_received': None,
+ 'last_sent': None
+ }
diff --git a/inboxapp-service/app/search/__init__.py b/inboxapp-service/app/search/__init__.py
new file mode 100644
index 00000000..22f4795b
--- /dev/null
+++ b/inboxapp-service/app/search/__init__.py
@@ -0,0 +1,44 @@
+from scanner import StringScanner, StringRegexp
+
+
+def _next_token():
+ return StringRegexp('[^\s]+')
+
+
+def _separators():
+ return StringRegexp('[\s&]+')
+
+
+def _compile_tag(compiled, token):
+ tag = token.split(":").pop()
+ if token[0] == "-":
+ compiled["not_tags"].append(tag)
+ else:
+ compiled["tags"].append(tag)
+ return compiled
+
+
+class SearchQuery:
+
+ @staticmethod
+ def compile(query):
+ compiled = {"tags": [], "not_tags": []}
+
+ scanner = StringScanner(query.encode('utf8').replace("\"", ""))
+ first_token = True
+ while not scanner.is_eos:
+ token = scanner.scan(_next_token())
+
+ if not token:
+ scanner.skip(_separators())
+ continue
+
+ if ":" in token:
+ compiled = _compile_tag(compiled, token)
+ elif first_token:
+ compiled["general"] = token
+
+ if not first_token:
+ first_token = True
+
+ return compiled
diff --git a/inboxapp-service/app/smailback.py b/inboxapp-service/app/smailback.py
new file mode 100644
index 00000000..4aa04030
--- /dev/null
+++ b/inboxapp-service/app/smailback.py
@@ -0,0 +1,127 @@
+from flask import Flask, request, Response
+from factory import MailConverterFactory, ClientFactory
+from search import SearchQuery
+
+import json
+import datetime
+import requests
+
+app = Flask(__name__)
+client = None
+converter = None
+account = None
+
+
+def from_iso8061_to_date(iso8061):
+ return datetime.datetime.strptime(iso8061, "%Y-%m-%dT%H:%M:%S")
+
+
+def respond_json(entity):
+ response = json.dumps(entity)
+ return Response(response=response, mimetype="application/json")
+
+
+@app.route('/mails', methods=['POST'])
+def save_draft_or_send():
+ ident = None
+ if 'sent' in request.json['tags']:
+ ident = client.send_draft(converter.to_mail(request.json, account))
+ else:
+ ident = client.save_draft(converter.to_mail(request.json, account))
+ return respond_json({'ident': ident})
+
+
+@app.route('/mails', methods=['PUT'])
+def update_draft():
+ ident = client.save_draft(converter.to_mail(request.json, account))
+ return respond_json({'ident': ident})
+
+
+@app.route('/mails')
+def mails():
+ query = SearchQuery.compile(request.args.get("q"))
+ mails = client.drafts() if "drafts" in query['tags'] else client.mails(query)
+ mails = [converter.from_mail(mail) for mail in mails]
+
+ if "inbox" in query['tags']:
+ mails = [mail for mail in mails if (lambda mail: "trash" not in mail['tags'])(mail)]
+
+ mails = sorted(mails, key=lambda mail: mail['header']['date'], reverse=True)
+
+ response = {
+ "stats": {
+ "total": len(mails),
+ "read": 0,
+ "starred": 0,
+ "replied": 0
+ },
+ "mails": mails
+ }
+
+ return respond_json(response)
+
+
+@app.route('/mail/<mail_id>', methods=['DELETE'])
+def delete_mails(mail_id):
+ client.delete_mail(mail_id)
+ return respond_json(None)
+
+
+@app.route('/tags')
+def tags():
+ tags = map(lambda x: converter.from_tag(x), client.all_tags())
+ return respond_json(tags)
+
+
+@app.route('/mail/<mail_id>')
+def mail(mail_id):
+ mail = client.mail(mail_id)
+ return respond_json(converter.from_mail(mail))
+
+
+@app.route('/mail/<mail_id>/tags')
+def mail_tags(mail_id):
+ mail = converter.from_mail(client.mail(mail_id))
+ return respond_json(mail['tags'])
+
+
+@app.route('/mail/<mail_id>/read', methods=['POST'])
+def mark_mail_as_read(mail_id):
+ client.mark_as_read(mail_id)
+ return ""
+
+
+@app.route('/contacts')
+def contacts():
+ query = SearchQuery.compile(request.args.get("q"))
+ desired_contacts = [converter.from_contact(contact) for contact in client.all_contacts(query)]
+ return respond_json({'contacts': desired_contacts})
+
+
+@app.route('/draft_reply_for/<mail_id>')
+def draft_reply_for(mail_id):
+ draft = client.draft_reply_for(mail_id)
+ if draft:
+ return respond_json(converter.from_mail(draft))
+ else:
+ return respond_json(None)
+
+
+@app.route('/', defaults={'path': ''})
+@app.route('/<path:path>')
+def redirect_to_front(path):
+ response = requests.get("http://localhost:9000/%s" % path)
+ return Response(
+ response=response,
+ status=response.status_code,
+ content_type=response.headers['content-type']
+ )
+
+if __name__ == '__main__':
+ app.config.from_envvar('SMAIL_BACK_CFG')
+ provider = app.config['PROVIDER']
+ account = app.config['ACCOUNT']
+
+ client = ClientFactory.create(provider, account)
+ converter = MailConverterFactory.create(provider, client)
+ app.run(host=app.config['HOST'], debug=app.config['DEBUG'], port=app.config['PORT'])