Sam Mussmann avatar Sam Mussmann committed fc4f1ca

Add app engine and update extension.

Comments (0)

Files changed (9)

appengine_app/app.yaml

+application: communication-infographic
+version: 1
+runtime: python27
+api_version: 1
+threadsafe: true
+
+handlers:
+- url: /.*
+  script: helloworld.app
+
+libraries:
+- name: jinja2
+  version: 2.6

appengine_app/format.raw

+{% for call in calls %}call, {{ call.start_time }}, {{ call.end_time }}
+{% endfor %}{% for m in sms %}sms, {{ m.sent_time }},
+{% endfor %}

appengine_app/helloworld.py

+import datetime
+import jinja2
+import json
+import os
+import webapp2
+
+from google.appengine.ext import db
+from google.appengine.api import users
+
+jinja_environment = jinja2.Environment(
+    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
+
+class PhoneCall(db.Model):
+    start_time = db.DateTimeProperty()
+    end_time = db.DateTimeProperty()
+    def to_dict(self):
+        return {'type': 'phone-call',
+                'start_time': str(self.start_time),
+                'end_time': str(self.end_time), }
+
+
+class Sms(db.Model):
+    sent_time = db.DateTimeProperty()
+    def to_dict(self):
+        return {'type': 'sms',
+                'sent_time': str(self.sent_time), }
+
+class MyUser(db.Model):
+    google_user = db.UserProperty()
+    def nickname(self):
+        return self.google_user.nickname()
+
+def parse_time(item):
+    return datetime.datetime.strptime(item['time'], '%m/%d/%y %I:%M %p')
+
+def parse_sms_time(item, time):
+    date = item['time'].split()[0]
+    return datetime.datetime.strptime(' '.join([date, time]),
+                                      '%m/%d/%y %I:%M %p')
+
+def parse_duration(item):
+    return datetime.timedelta(minutes = int(item['duration'].split()[0]))
+
+def init_user():
+    user = users.get_current_user()
+    if not user:
+        return None
+    my_user = MyUser.get(db.Key.from_path('MyUser', user.user_id()))
+    if not my_user:
+        my_user = MyUser(key_name=user.user_id())
+        my_user.google_user = user
+        my_user.put()
+    return my_user
+
+class Stats(webapp2.RequestHandler):
+    def get(self):
+        user = init_user()
+        if not user:
+            self.redirect(users.create_login_url(self.request.uri))
+            return
+
+        durations = [c.end_time - c.start_time for c in PhoneCall.all().ancestor(user).order('start_time')]
+        durations.sort()
+        sum_durations = sum(durations, datetime.timedelta())
+        def avg_duration(duration_list):
+            sum_duration_list = sum(duration_list, datetime.timedelta())
+            return datetime.timedelta(seconds=sum_duration_list.total_seconds() / len(duration_list))
+
+
+        self.response.headers['Content-Type'] = 'text/plain'
+        self.response.out.write('total: %s\n' % str(sum_durations))
+        self.response.out.write('#: %d\n' % len(durations))
+        self.response.out.write('mean: %s\n' % str(avg_duration(durations)))
+        self.response.out.write('median: %s\n' % durations[len(durations) / 2])
+        self.response.out.write('max: %s\n' % durations[-1])
+        long_durations = filter(lambda x: x > datetime.timedelta(minutes=5), durations)
+        long_durations.sort()
+        self.response.out.write('# >5: %d\n' % len(long_durations))
+        self.response.out.write('mean >5: %s\n' % str(avg_duration(long_durations)))
+        self.response.out.write('median >5: %s\n' % long_durations[len(long_durations) / 2])
+        
+
+class Deleter(webapp2.RequestHandler):
+    def get(self):
+        user = init_user()
+        if not user:
+            self.redirect(users.create_login_url(self.request.uri))
+            return
+        self.response.out.write('''<form method='POST'>Do you want to delete all your data?
+          <input type='submit' action='Yes!'></form>''')
+    def post(self):
+        user = init_user()
+        if not user:
+            self.redirect(users.create_login_url(self.request.uri))
+            return
+        for call in PhoneCall.all().ancestor(user).order('start_time'):
+            call.delete()
+        for sms in Sms.all().ancestor(user).order('sent_time'):
+            sms.delete()
+        self.redirect('/')
+
+class RawAccess(webapp2.RequestHandler):
+    def get(self):
+        user = init_user()
+        if not user:
+            self.redirect(users.create_login_url(self.request.uri))
+            return
+        
+        data = {
+            'calls': map(lambda x: x.to_dict(), list(PhoneCall.all().ancestor(user).order('start_time'))),
+            'sms': map(lambda x: x.to_dict(), list(Sms.all().ancestor(user).order('sent_time'))),
+        }
+        self.response.headers['Content-Type'] = 'text/plain'
+        self.response.out.write(json.dumps(data))
+
+class MainPage(webapp2.RequestHandler):
+    def get(self):
+        user = init_user()
+        if not user:
+            self.redirect(users.create_login_url(self.request.uri))
+            return
+        template_values = {
+            'calls': PhoneCall.all().ancestor(user).order('start_time'),
+            'sms': Sms.all().ancestor(user).order('sent_time'),
+            'user': user,
+        }
+        template = jinja_environment.get_template('index.html')
+        self.response.out.write(template.render(template_values))
+
+    def post(self):
+        user = init_user()
+        if not user:
+            self.redirect('/')
+            return
+        for item in json.loads(self.request.arguments()[0]):
+            if item['type'] == 'sms':
+                for time in item['times']:
+                    sms = Sms(parent=user)
+                    sms.sent_time = parse_sms_time(item, time)
+                    sms.put()
+                    self.response.out.write(sms)
+            if item['type'] == 'received-call' or item['type'] == 'placed-call':
+                call = PhoneCall(parent=user)
+                call.start_time = parse_time(item)
+                call.end_time = call.start_time + parse_duration(item)
+                call.put()
+                self.response.out.write(call)
+
+
+app = webapp2.WSGIApplication([('/', MainPage),
+                               ('/raw', RawAccess),
+                               ('/delete', Deleter),
+                               ('/stats', Stats),
+                               ], debug=True)
+

appengine_app/index.html

+<html>
+<head>
+<title>Homepage for {{ user.nickname() }}</title>
+</head>
+<body>
+<table>
+{% for call in calls %}
+<tr><td>{{ call.start_time }}</td><td>{{ call.end_time }}</td></tr>
+{% endfor %}
+{% for m in sms %}
+<tr><td>{{ m.sent_time }}</td><td></td></tr>
+{% endfor %}
+</table>
+</body>

appengine_app/index.yaml

+indexes:
+- kind: PhoneCall
+  ancestor: yes
+  properties:
+  - name: start_time
+    direction: asc
+- kind: Sms
+  ancestor: yes
+  properties:
+  - name: sent_time
+    direction: asc
+
+import imaplib
+import email
+import pprint
+import re
+
+mail = imaplib.IMAP4_SSL('imap.gmail.com')
+password = 'nvlppmxgghbwcbbc'
+mail.login('sdmuss@gmail.com', password)
+mail.capability()
+mail.select('[Gmail]/Chats', readonly=True)
+result, data = mail.search(None, 'X-GM-RAW', 'from:annabookreader@gmail.com OR from:anna.beck@immanuelalexandria.org')
+result, messages = mail.fetch(','.join(data[0].split()), '(BODY[HEADER.FIELDS (DATE)])')
+messages = filter(lambda s: s != ')', messages)
+chats = map(lambda t: t[1], messages)
+
+class Message(object):
+    def __init__(self, header_string):
+        headers = filter(lambda x: x != '', header_string.split('\r\n'))
+        self.addresses = re.findall(r'<([^>]*)>', ' '.join(headers))
+        self.date = header_string.split('\r\n', 1)[0]
+
+    def has_only_addresses(self, addresses):
+        return len(filter(lambda x: x not in addresses, self.addresses)) == 0
+       
+
+email_addresses = ['annabookreader@gmail.com', 'anna.beck@immanuelalexandria.org', 'ilonabeck@yahoo.com']
+def from_and_to(email_addresses):
+	result = []
+	for email in email_addresses:
+		result.append('from:' + email)
+		result.append('to:' + email)
+	return ' OR '.join(result)
+mail.select('[Gmail]/All Mail')
+result, data = mail.search(None, 'X-GM-RAW', from_and_to(email_addresses))
+result, messages = mail.fetch(','.join(data[0].split()), '(BODY[HEADER.FIELDS (DATE FROM TO)])')
+messages = filter(lambda s: s != ')', messages)
+messages = map(lambda t: Message(t[1]), messages)
+acceptable_addresses = email_addresses + ['sdmuss@gmail.com', 'sam@sammussmann.net']
+emails = map(lambda m: m.date, filter(lambda m: m.has_only_addresses(acceptable_addresses), messages))

gvoice_extension/background.js

     console.log('executing...');
     chrome.tabs.executeScript(null, {file: "gvoice.js"});
 });
+
+var call_list = Array();
+chrome.extension.onRequest.addListener(
+    function(request) {
+        console.log(request);
+        call_list = call_list.concat(request);
+        console.log(call_list);
+    });
+

gvoice_extension/gvoice.js

             16: 'placed-call',
             17: 'placed-call'};
 var url = document.location.hash;
+var out_list = Array();
 var messages = document.getElementsByClassName('gc-message-tbl');
 for (var i = 0; i < messages.length; i++) {
     var message = messages[i];
     if (msg_out.duration) msg_out.duration = msg_out.duration.textContent;
     msg_out.raw_type = message.getElementsByClassName('gc-message-portrait')[0].lastElementChild.className.split('-')[3];
     msg_out.type = type_map[msg_out.raw_type];
-    console.log(msg_out);
+    if (msg_out.type == 'sms') {
+        msg_out.times = Array();
+        var times = message.getElementsByClassName('gc-message-sms-time');
+        for (var j = 0; j < times.length; j++) {
+            msg_out.times.push(times[j].innerText);
+        }
+    }
+    out_list.push(msg_out);
 }
+console.log('next round');
+if (/.*p[0-9]+/.test(url)) {
+    var page_num_idx = url.search(/\d+$/);
+    var page_num = url.slice(page_num_idx);
+    url = url.slice(0, page_num_idx) + (parseInt(page_num) + 1);
+    document.location.hash = url;
+} else {
+    document.location.hash = url + '/p2';
+}
+chrome.extension.sendRequest(out_list);
+var xhr = new XMLHttpRequest();
+xhr.open("POST", "http://localhost:8080/", false)
+xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+xhr.send(JSON.stringify(out_list));
+
+console.log(xhr.responseText);

gvoice_extension/manifest.json

     "permissions": [
         "notifications",
         "tabs",
-        "https://www.google.com/voice/*"
+        "https://www.google.com/voice/*",
+        "http://localhost:8080/"
     ],
     "browser_action": {},
     "background": {"scripts": ["background.js"]}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.