Skip to content

Commit

Permalink
Also add profile pictures from WhatsApp
Browse files Browse the repository at this point in the history
  • Loading branch information
Bibo-Joshi committed Nov 28, 2021
1 parent 1f2d864 commit 9470dac
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 2 deletions.
17 changes: 17 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Running `main.py` basically does the following:
1. Download all contacts that already exist in the address book
2. Download the AkaDressen and parse them into vCards
3. Optionally look up the phone numbers and get the corresponding profile pictures from Telegram
3. Optionally look up the phone numbers and get the corresponding profile pictures from WhatsApp (see below)
4. Merge the vCards generated from the AkaDressen into the existing ones. A few notes on this:
1. Existing data is never overwritten - at most, it's appended
2. vCards are tricky and this is as best effort solution. You may need to manually tweak the result
Expand All @@ -31,3 +32,19 @@ Note that not all methods & classes have elaborate documentation, but all user f
```shell
$ python main.py
```

## A note on profile pictures

Telegram has an open API so that you can get all your contacts profile pictures programmatically.
So this part is easy.

WhatsApp doesn't have such a functionality - there is an API, but it's only for business customers.
What we *can* do is to snatch the profile pictures when they are loaded to be shown to you.
Here is how it's done:

1. Opening web.whatsapp.com
2. Opening the dev tools of your browser and switching to the network tab
3. Scrolling through all your contacts & member lists of the relevant groups
4. Exporting the network log as HAR file

From this file, we can extract the profile pictures.
2 changes: 2 additions & 0 deletions akadressen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"get_akadressen_vcards",
"merge_vcards",
"add_telegram_profile_pictures_to_vcards",
"add_whatsapp_profile_pictures_to_vcards",
]

from ._ncaddressbook import NCAddressBook
from ._parse_akadressen import get_akadressen_vcards
from ._merge_vcards import merge_vcards
from ._telegram import add_telegram_profile_pictures_to_vcards
from ._whatsapp import add_whatsapp_profile_pictures_to_vcards
2 changes: 1 addition & 1 deletion akadressen/_ncaddressbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ async def refresh_contacts(self, clear: bool = True) -> list[str]:
element = ET.XML(xml_data)

for entry in element.iter(): # pylint: disable=too-many-nested-blocks
if entry.tag == namespace + "entry":
if entry.tag in [namespace + "entry", namespace + "response"]:
uid = ""
etag = ""
insert = False
Expand Down
82 changes: 82 additions & 0 deletions akadressen/_whatsapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python
"""A module containing functionality to add profile pictures from WhatsApp to vCards based on
the phone number."""
import base64
import json
from logging import getLogger
from pathlib import Path
from typing import Union, Sequence

import vobject.base
from akadressen._util import ProgressLogger

_logger = getLogger(__name__)


def _add_photo_to_vcard(
vcard: vobject.base.Component,
photo: bytes,
) -> None:
if vcard.contents.get("photo"):
return

photo_component = vcard.add("photo")
photo_component.encoding_param = "B"
photo_component.type_param = "JPG"
photo_component.value = photo


def add_whatsapp_profile_pictures_to_vcards(
vcards: Sequence[vobject.base.Component], network_har_log: Union[str, Path]
) -> None:
"""Adds profile pictures to all vCards that can be found on WhatsApp. Requires you to provide
the photos by:
1. Opening web.whatsapp.com
2. Opening the dev tools of your browser and switching to the network tab
3. Scrolling through all your contacts & member lists of the relevant groups
4. Exporting the network log as HAR
Args:
vcards (List[:class:`vobject.base.Component`]): The vcards. Will be edited in place.
network_har_log (:obj:`str` | :class:`pathlib.Path`): Path to the network logs.
"""
_logger.debug("Processing WhatsApp network logs.")
if not (data := json.loads(Path(network_har_log).read_bytes()).get("log")):
raise RuntimeError("The data is not in a format that I can handle.")

if not (entries := data.get("entries")):
raise RuntimeError("The data is not in a format that I can handle.")

photo_map: dict[str, bytes] = {}
for entry in entries: # pylint: disable=too-many-nested-blocks
if (request := entry.get("request")) and (response := entry.get("response")):
if (url := request.get("url")) and (content := response.get("content")):
if mime_type := content.get("mimeType"):
if isinstance(mime_type, str) and mime_type.startswith("image/"):
image = content.get("text")
encoding = content.get("encoding")

if encoding.lower() == "base64":
photo_map[url] = base64.b64decode(image)

_logger.debug("Checking vCards against found contacts.")
progress_logger = ProgressLogger(
_logger, len(vcards), message="Checked %d of %d vCards.", modulo=50
)

for vcard in vcards:
if not (tel := vcard.contents.get("tel")):
continue

for entry in tel:
phone_number = entry.value.strip().replace("/", "").replace(" ", "")
for url, photo in photo_map.items():
if phone_number in url or (
phone_number.startswith("0") and phone_number[1:] in url
):
_add_photo_to_vcard(vcard, photo)
continue

progress_logger.log()
4 changes: 4 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from akadressen import (
NCAddressBook,
add_telegram_profile_pictures_to_vcards,
add_whatsapp_profile_pictures_to_vcards,
merge_vcards,
get_akadressen_vcards,
)
Expand Down Expand Up @@ -58,6 +59,9 @@ async def main() -> None:
session_name="telegram",
)

# Optionally, add profile pictures from WhatsApp
add_whatsapp_profile_pictures_to_vcards(aka_vcards, "web.whatsapp.com.har")

# Merge AkaDressen into existing contacts
merge_vcards(
directory=GENERATED_VCARDS_PATH,
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ignore = W503, W605
extend-ignore = E203

[pylint.message-control]
disable = too-many-locals,too-many-branches
disable = too-many-locals,too-many-branches,R0801

[mypy]
warn_unused_ignores = True
Expand Down

0 comments on commit 9470dac

Please sign in to comment.