Skip to content

Commit

Permalink
whatsapp module
Browse files Browse the repository at this point in the history
  • Loading branch information
manu committed Mar 1, 2017
1 parent 814fd28 commit f54d28a
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
23 changes: 23 additions & 0 deletions bin/memacs_whatsapp.py
@@ -0,0 +1,23 @@
#!/usr/bin/python2
# -*- coding: utf-8 -*-

from memacs.whatsapp import WhatsApp

PROG_VERSION_NUMBER = u"0.1"
PROG_VERSION_DATE = u"2017-02-28"
PROG_SHORT_DESCRIPTION = u"Memacs for whatsapp"
PROG_TAG = u"whatsapp"

COPYRIGHT_YEAR = "2017"
COPYRIGHT_AUTHORS = """Manuel Koell <mankoell@gmail.com>"""

if __name__ == "__main__":
memacs = WhatsApp(
prog_version=PROG_VERSION_NUMBER,
prog_version_date=PROG_VERSION_DATE,
prog_short_description=PROG_SHORT_DESCRIPTION,
prog_tag=PROG_TAG,
copyright_year=COPYRIGHT_YEAR,
copyright_authors=COPYRIGHT_AUTHORS
)
memacs.handle_main()
32 changes: 32 additions & 0 deletions docs/memacs_whatsapp.org
@@ -0,0 +1,32 @@
## This file is best viewed with GNU Emacs Org-mode: http://orgmode.org/

* memacs-whatsapp

In order to use this memacs module you need the decrypted msgstore.db file.
The easiest way is to use an android phone (rooted) and copy the file from
~/data/data/com.whatsapp/databases/msgstore.db~.

** Options

- ~-f~, ~--file~ path to decrypted msgstore.db file
- ~--ignore-incoming~ ignore received messages
- ~--ignore-outgoing~ ignore sent messages
- ~--output-format~ format string to use for the headline, default ~{verb} [[{handler}:{number}][{number}]]: {text}~
- ~--set-handler~ set link handler, default ~tel~
- ~--demojize~ replace emoji with the appropriate ~:short_code:~
- ~--skip-emoji~ skip all emoji (including emoji only messages)

** Example

#+BEGIN_EXAMPLE
memacs_whatsapp.py -f /path/to/msgstore.db --output-format '{text}'
#+END_EXAMPLE

#+BEGIN_EXAMPLE
** <2016-10-15 Sat 20:18> Hello World!
:PROPERTIES:
:TYPE: INCOMING
:NUMBER: 00436604444333
:ID: d8fa64a86286432a42a13f129cad0d7ebd56873e
:END:
#+END_EXAMPLE
Binary file added memacs/tests/data/msgstore.db
Binary file not shown.
34 changes: 34 additions & 0 deletions memacs/tests/whatsapp_test.py
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Time-stamp: <2011-10-28 15:13:31>

import os
import time
import unittest

from memacs.whatsapp import WhatsApp


class TestWhatsApp(unittest.TestCase):

def setUp(self):
msgstore = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'data', 'msgstore.db'
)

self.argv = []
self.argv.append('-f')
self.argv.append(msgstore)

def test_whatsapp(self):
self.argv.append('--output-format')
self.argv.append('{text}')

memacs = WhatsApp(argv=self.argv)
data = memacs.test_get_entries()

self.assertEqual(data[0], '** <2016-10-15 Sat 20:18> Hello World!')
self.assertEqual(data[1], ' :PROPERTIES:')
self.assertEqual(data[2], ' :TYPE: INCOMING')
self.assertEqual(data[3], ' :NUMBER: 00436604444333')
self.assertEqual(data[4], ' :ID: d8fa64a86286432a42a13f129cad0d7ebd56873e')
self.assertEqual(data[5], ' :END:')
121 changes: 121 additions & 0 deletions memacs/whatsapp.py
@@ -0,0 +1,121 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Time-stamp: <2017-02-07 19:25 manu>

import sqlite3
import logging
import datetime
import json
import time
import sys
import os
import re

import emoji

from lib.orgproperty import OrgProperties
from lib.orgformat import OrgFormat
from lib.memacs import Memacs


class WhatsApp(Memacs):
def _parser_add_arguments(self):
"""
overwritten method of class Memacs
add additional arguments
"""
Memacs._parser_add_arguments(self)

self._parser.add_argument(
"-f", "--file", dest="msgstore",
action="store", type=file, required=True,
help="path to decrypted msgstore.db file")

self._parser.add_argument(
"--ignore-incoming", dest="ignore_incoming",
action="store_true", help="ignore received messages")

self._parser.add_argument(
"--ignore-outgoing", dest="ignore_outgoing",
action="store_true", help="ignore sent messages")

self._parser.add_argument(
"--output-format", dest="output_format",
action="store", default="{verb} [[{handler}:{number}][{number}]]: {text}",
help="format string to use for the headline")

self._parser.add_argument(
"--set-handler", dest="handler",
action="store", default="tel",
help="set link handler")

self._parser.add_argument(
"--demojize", dest="demojize",
action="store_true", help="replace emoji with the appropriate :shortcode:")

self._parser.add_argument(
"--skip-emoji", dest="skip_emoji",
action="store_true", help="skip all emoji")

def _parser_parse_args(self):
"""
overwritten method of class Memacs
all additional arguments are parsed in here
"""
Memacs._parser_parse_args(self)

def _is_ignored(self, msg):
"""check for ignored message type"""

if msg['type'] is 'INCOMING' and self._args.ignore_incoming:
return True

if msg['type'] is 'OUTGOING' and self._args.ignore_outgoing:
return True

def _handle_message(self, msg):
"""parse a single message row"""

msg['number'] = '00' + msg['number'].split('@')[0]
msg['verb'] = 'to' if msg['type'] else 'from'
msg['type'] = 'OUTGOING' if msg['type'] else 'INCOMING'
msg['handler'] = self._args.handler

if msg['text']:
if self._args.demojize:
msg['text'] = emoji.demojize(msg['text'])

if self._args.skip_emoji:
msg['text'] = re.sub(emoji.get_emoji_regexp(), '', msg['text'])

timestamp = datetime.datetime.fromtimestamp(msg['timestamp'] / 1000)

properties = OrgProperties(data_for_hashing=json.dumps(msg))
properties.add('NUMBER', msg['number'])
properties.add('TYPE', msg['type'])

output = self._args.output_format.decode('utf-8').format(**msg)

if msg['text'] and not self._is_ignored(msg):
self._writer.write_org_subitem(timestamp=OrgFormat.datetime(timestamp),
output=output, properties=properties)

def _main(self):
"""
get's automatically called from Memacs class
"""

conn = sqlite3.connect(os.path.abspath(self._args.msgstore.name))
query = conn.execute('SELECT * FROM messages')

for row in query:
self._handle_message({
'timestamp': row[7],
'number': row[1],
'type': row[2],
'text': row[6]
})

logging.debug(row)

0 comments on commit f54d28a

Please sign in to comment.