If you have ever sent links to yourself in Telegram in an attempt to bookmark it (like I sometimes do), or if you just want to extract all links that people have sent you and transfer them over to your bookmark manager, this post is for you. I was recently in this situation, and using a combination of Docker, a Telegram CLI tool, and a simple Python script, I was able to construct a Netscape Bookmarks file, which I successfully imported into my Pinboard account. This post describes the steps I followed, which I’m documenting here for posterity.

Telegram has an open API with a number of open source libraries and clients providing access to it in various ways. Among these is the excellent Telegram messenger CLI tg. But alas, this project is unmaintained (as many projects in Github are wont to be). Moreover, neither this nor the more current fork, works out of the box on macOS Catalina1. Thankfully, there is a docker image available that comes with this client pre-installed:

  1. Start off with installing docker from their official webpage.
  2. Create a directory where you want to put the resulting bookmarks file: mkdir ~/telegram-bookmarks && cd ~/telegram-bookmarks
  3. Then pull and run the weibeld/ubuntu-telegram-cli from the commandline using:
    docker run -ti weibeld/ubuntu-telegram-cli
    

    which might take a while to finish running. As described on its webpage,

    This takes you into a Bash session on Ubuntu in the /root/tg directory. where you can run the telegram cli.

  4. Run, bin/telegram-cli -k tg-server.pub and follow the prompts to log into your Telegram account. You will have to enter the Login code that is sent to your other Telegram client(s). Once you successfully reach the interactive mode, exit it by pressing Ctrl-C. This step caches your API credentials, so that you can run your next command without the interactive mode.
  5. Run the following command to generate a json dump of all the messages in a particular chat:
     bin/telegram-cli -CR -k tg-server.pub -e 'history <DialogName> 10000'  -W --json  > links.out.json
    

    where you would replace <DialogName> with the name of the chat/user where you have your links. You can get a list of dialog/chat/user names with bin/telegram-cli -CR -k tg-server.pub -e 'dialog_list' -W

  6. Now we need to copy the file from the previous step out of the docker instance. First get the name or id of the docker instance from above by running docker container ls. Then, outside the docker shell, in the ~/telegram-bookmarks directory, run docker cp <instance name>:/root/tg/links.out.json . to copy the file from the docker instance into the local directory.
  7. Edit the links.out.json file to remove everything that is not the json we need. Starting with a file that looks like this:
     Telegram-cli version 1.4.1, Copyright (C) 2013-2015 Vitaly Valtman
     Telegram-cli comes with ABSOLUTELY NO WARRANTY; for details type `show_license'.
     This is free software, and you are welcome to redistribute it
     under certain conditions; type `show_license' for details.
     Telegram-cli uses libtgl version 2.1.0
     Telegram-cli includes software developed by the OpenSSL Project
     for use in the OpenSSL Toolkit. (http://www.openssl.org/)
     I: config dir=[/root/.telegram-cli]
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 12345, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 12343, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 12343, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 12345, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 12341, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234 "print_name": "", "flags": 5}}
     {"updates": ["deleted"], "event": "updates", "peer": {"id": "asdf", "peer_type": "user", "peer_id": 1234, "print_name": "", "flags": 5}}
     [{"media": {"type": "webpage", "url": "https://articles.tilt365.com/identify-destructive-leadership-patterns/", "title": "How to Identify Destructive Leadership Patterns", "description": "In many aspects of our lives, we rely on those in positions of power to lead us. The role of leaders becomes especially salient in times of uncertainty.", "author": "https://www.facebook.com/pamboney"}, "event": "message", "unread": false, "id": "asdf", "flags": 256, "from": {"id": "$asdf", "peer_type": "user", "peer_id": 1234, "when": "2020-12-17 00:48:32", "print_name": "Anand", "flags": 720897, "last_name": "", "first_name": "Anand", "phone": "1234123123", "username": "asdf"}, "to": {"id": "$asdf", "peer_type": "user", "peer_id": 1234, "when": "2020-12-17 00:48:32", "print_name": "Anand", "flags": 720897, "last_name": "", "first_name": "Anand", "phone": "1234123123", "username": "asdf"}, "text": "https://articles.tilt365.com/identify-destructive-leadership-patterns/", "out": false, "service": false, "date": 1604845326}, ...]
     halt
     All done. Exit
    

    delete all the lines except the relevant json, to end up with a file that looks like this:

     [{"media": {"type": "webpage", "url": "https://articles.tilt365.com/identify-destructive-leadership-patterns/", "title": "How to Identify Destructive Leadership Patterns", "description": "In many aspects of our lives, we rely on those in positions of power to lead us. The role of leaders becomes especially salient in times of uncertainty.", "author": "https://www.facebook.com/pamboney"}, "event": "message", "unread": false, "id": "asdf", "flags": 256, "from": {"id": "$asdf", "peer_type": "user", "peer_id": 1234, "when": "2020-12-17 00:48:32", "print_name": "Anand", "flags": 720897, "last_name": "", "first_name": "Anand", "phone": "1234123123", "username": "asdf"}, "to": {"id": "$asdf", "peer_type": "user", "peer_id": 1234, "when": "2020-12-17 00:48:32", "print_name": "Anand", "flags": 720897, "last_name": "", "first_name": "Anand", "phone": "1234123123", "username": "asdf"}, "text": "https://articles.tilt365.com/identify-destructive-leadership-patterns/", "out": false, "service": false, "date": 1604845326}, ...]
    
  8. At this point, you can process the json however you want. To generate a html file in the Netscape Bookmarks file format, run this Python script in the same directory where you put the .json file.

Et voilà! Now you can import your links into any bookmark manager of your choice.

  1. You might have better luck installing the client directly in other operating systems