Skip to content

Latest commit

 

History

23 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

telepad

You know the feeling: dozens — hundreds — of Telegram chats, groups, channels and DMs, and finding the right one means scrolling, half-remembering names, and searching inside the app. It's like having a thousand browser tabs open, except worse — they're spread across multiple accounts and buried under folders and archives.

telepad is a rofi quick-switcher for Telegram — hit a hotkey from anywhere, fuzzy-type a few letters, and your running Telegram Desktop (or a fork like AyuGram) jumps straight to that chat, group, channel, contact, or forum topicacross all your accounts.

It's Discord's Ctrl+K quick-switcher, reimagined for the desktop Telegram client — and arguably better. Because it's a global rofi keybinding (bound in your WM), you trigger it from any window: your editor, terminal, browser, anything. No need to first focus the app. Where Discord makes you focus Discord → Ctrl+K → type → jump, telepad is just hotkey → type → jump from wherever you already are — fewer keystrokes, zero context switch.

┌─ data layer (grammers / MTProto) ─┐        ┌─ front-end (rofi) ─────────────────┐
│ per account, once: log in         │        │ flat fuzzy list across accounts    │
│ sync: dialogs + contacts + forum  │ ─cache→ │  ↳ forum? → topic submenu          │
│       topics → JSON cache          │        │ → (inject_cmd: switch account)     │
└────────────────────────────────────┘        │ → D-Bus Open → client navigates   │
                                              └────────────────────────────────────┘

Status: works day-to-day for the author on X11 + i3 + AyuGram. Rough edges remain — see Caveats and Roadmap. The client is now configurable (client = "telegram" | "ayugram"), defaulting to vanilla Telegram Desktop, but has only been exercised in anger against AyuGram so far — some tg:// id-based opens may still differ on vanilla (see Caveats).

Features

  • Fuzzy jump to any chat, group, channel, or contact, across all accounts — searchable by display name or @handle (public usernames show in the row)
  • Forum topics: every topic is a flat, searchable row (Forum ▸ Topic · topic) so you can jump straight to one on the first keystroke — or select the forum itself to browse its topics in a submenu
  • Chat folders: a 📁 <Folder> entry per account expands into a submenu of that folder's chats
  • Archive: a 🗄 Archived entry per account — open the archive folder, or jump straight to any archived chat
  • Contacts included — even blocked users or people you have no open chat with
  • Cross-account: switches to the target account before opening
  • Frecency ranking: chats you jump to often (and recently) float to the top, so the list stays sorted around how you actually use it
  • Saved Messages: a ⭐ Saved Messages row per account, always a keystroke away
  • Avatars (opt-in): sync --avatars caches profile photos and rofi shows them as row icons
  • Fullscreen rofi menu; simple login / sync / menu commands

How it works

Two halves that talk through a small on-disk cache:

  • Reading your chats uses grammers, a Rust MTProto client. This is a separate login from AyuGram (it can't read AyuGram's encrypted tdata), so each account authenticates once and its session is cached. sync pulls the dialog list, your contacts, each forum's topics, and your chat folders into a JSON cache the menu reads.
  • Navigating hands a tg:// link to the running client in-process over D-Bus (org.freedesktop.Application.Open on the client's well-known name — org.telegram.desktop for Telegram, com.ayugram.desktop for AyuGram, picked by the client setting). Public peers resolve by @username; private channels/supergroups open via privatepost?channel=<raw>; other username-less peers (users, legacy groups) open by internal peer id via tg://chat?id=; forum topics use resolve?domain=X&topic= (public) or privatepost?channel=<raw>&topic= (private).

Account switching (the ugly part)

Switching account is done by injecting the keypress you bound in the client (e.g. alt+2) via a configurable key-injector (inject_cmd, default xdotool), not by any tg:// parameter. This is deliberate and worth understanding:

  • tg://…&acc=N crashes AyuGram. The deep-link account-switch path segfaults in the builds tested (on Nix it dies silently — no crash dump, truncated log).
  • xdg-open tg://… spawns a second client process; its single-instance handoff races and can kill the running window. So navigation uses the in-process D-Bus Open instead, which never spawns anything.
  • That leaves no safe URL-based way to switch accounts, so telepad drives the client's own account-switch shortcut with a real keypress — the same switch you'd do by hand, which is safe.

Consequently, cross-account jumping needs two configurable shell commands — a window-focuser (focus_cmd, default i3-msg) and a key-injector (inject_cmd, default xdotool) — plus the account-switch keys bound inside the client. Both default to X11 + i3 but are overridable for any WM, X11 or Wayland (see Configuration). Leave switch_key empty to stay same-account only (rock-solid, no focuser/injector needed).

Requirements

  • Rust (to build)
  • rofi — the menu
  • gdbus (GLib) — delivers the URL to the running client
  • for cross-account switching only: a window-focuser and a key-injector — defaults i3-msg + xdotool (X11); override focus_cmd / inject_cmd for other WMs (e.g. swaymsg/hyprctl + ydotool on Wayland)

Install

cargo build --release
# put target/release/telepad on your PATH

Setup

  1. API credentials (free): https://my.telegram.org → API development tools → note your api_id and api_hash.
  2. Bind account-switch keys in the client (for cross-account): AyuGram/Telegram Desktop → Settings → Advanced → keyboard shortcuts → bind account1, account2, … to keys (e.g. alt+1, alt+2).
  3. Configure telepad:
    mkdir -p ~/.config/telepad
    cp config.example.toml ~/.config/telepad/config.toml
    $EDITOR ~/.config/telepad/config.toml
    Set api_id/api_hash and one [[accounts]] block per account, giving each its switch_key.
  4. Log in each account once:
    telepad login personal

    The login code arrives inside your already-logged-in Telegram/AyuGram (the Telegram service chat), not by SMS. 2FA password is prompted if set.

  5. Build the cache:
    telepad sync            # all accounts (dialogs + contacts + forum topics)
  6. Bind the menu to a key (i3 example): bindsym $mod+g exec --no-startup-id telepad

Usage

telepad                 # the quick-switcher (flat, all accounts)
telepad menu            # same thing
telepad sync [acct]     # refresh the cache (run periodically / via cron)
telepad sync --avatars  # also download profile photos (slower; shown as row icons)
telepad login <acct>    # (re)authenticate an account

Rows are tagged [Account], so type an account name to scope (work signals) or just the chat name (signals). Peers with a public username show their @handle in the row, so you can search by either the display name or the handle (@durov). Forum rows show forum ▸, and each of their topics appears as its own Forum ▸ Topic · topic row for direct jumping. Chat folders show as 📁 <Folder> ▸ rows that expand into that folder's chats.

Config reference

Field Meaning
api_id / api_hash One app from my.telegram.org, shared by all accounts
client Which client to drive: telegram (default) or ayugram. Sets the D-Bus name + window class
dbus_service Override the client's D-Bus name (for other TDesktop forks); object path is derived from it
window_class Override the X11 class of the client, focused before the switch key (else from client)
focus_cmd Shell command to focus the client; {class} substituted. Default i3-msg [class="{class}"] focus. Override for other WMs (wmctrl, swaymsg, hyprctl)
inject_cmd Shell command to press a key chord; {key} substituted. Default xdotool key --clearmodifiers {key} (X11). Override for Wayland (ydotool, wtype)
accounts[].acc 1-based slot in the client's account list (display/ordering)
accounts[].label Name shown in rofi
accounts[].session Session file name under ~/.local/share/telepad/
accounts[].phone International format; used only for telepad login
accounts[].switch_key Key chord that switches to this account (e.g. alt+1), sent via inject_cmd; empty = never switch

Caveats

  • Client is configurable but only battle-tested on AyuGram. The D-Bus name and window class now follow the client setting (telegram / ayugram), so vanilla Telegram Desktop is wired up and the default. But the tg:// handlers differ between builds: AyuGram has a tg://chat?id= handler used for username-less users/legacy groups, which vanilla Telegram Desktop may not — those specific jumps could still fail there (public @username peers and private channels via privatepost should work on both). Real-world vanilla testing is still on the TODO.
  • Cross-account depends on a focuser + key-injector and on window focus/timing. Defaults are X11 (i3-msg + xdotool); Wayland works by pointing focus_cmd / inject_cmd at swaymsg/hyprctl + ydotool. Same-account is dependency-light.
  • Separate sessions: grammers logs in independently, so each account shows as an extra device in your Telegram sessions list.
  • Stale cache: the list is a snapshot; re-run telepad sync to pick up new chats.
  • "Recents" that aren't saved contacts won't appear (only dialogs + saved contacts are indexed).
  • Chat folders list their explicitly-added chats. Folders defined purely by rules ("all groups", "unread", "non-contacts") with no named members won't populate, and selecting a folder chat doesn't switch the client's sidebar tab.
  • "Creative" names are hard to find. As always, if someone gives a channel or nickname a name in a fancy font — those Unicode "𝔤𝔬𝔱𝔥𝔦𝔠" / "𝓼𝓬𝓻𝓲𝓹𝓽" / fullwidth pseudo-fonts that aren't actually plain letters but styled math/Unicode codepoints — it won't match a normal-text fuzzy search. Typing signals won't find 𝓼𝓲𝓰𝓷𝓪𝓵𝓼, because to the matcher they're entirely different characters. telepad indexes the raw name as-is and does no Unicode font-normalization, so you'd have to recognize and scroll to those visually.

Roadmap / TODO

  • Test against vanilla Telegram Desktop. The D-Bus name and window class are now configurable via client (done); what's left is real-world verification and handling any tg:// handler gaps on vanilla (notably the id-based chat?id= open, which AyuGram provides but vanilla may not).

  • Decouple from rofi — become composable plumbing. The WM/injector are already configurable (focus_cmd / inject_cmd); the picker (rofi) is the last hardcoded touchpoint. Turn telepad into Unix-pipe primitives so anyone can bring their own picker too: - telepad list prints every jumpable target as a flat, machine-readable stream (one line per chat / topic / folder-chat / archived-chat). - telepad open takes a selected line back (stdin/arg), maps it to its target by re-deriving the same list from cache, then runs switch + open. - telepad menu stays the batteries-included default (pipes list into the menu command and dispatches open), so it's still zero-config — but power users can just do telepad list | fzf | telepad open.

    All four external touchpoints become configurable commands with `{…}`
    placeholder substitution, exactly like today's `focus_cmd` / `{class}`:
    
    | hook | default | unlocks |
    |------|---------|---------|
    | `menu_cmd`   | `rofi -dmenu` | dmenu / fzf / wofi / any picker |
    | `focus_cmd`  | i3-msg *(exists today)* | other WMs |
    | `inject_cmd` | xdotool *(exists today)* | ydotool / Wayland |
    | `open_cmd`   | gdbus … `{service}` … `{url}` | any opener (already: `client`/`dbus_service` pick the D-Bus name) |
    
    A single flat `list` stream implies folders/archive would render as flat
    rows (`📁 Folder ▸ Chat`, `🗄 Chat`) instead of interactive submenus —
    mirroring how forum topics are already flattened. The D-Bus name is already
    configurable (`client` / `dbus_service`); `open_cmd` would generalize the whole
    open command. Goal: few/no hardcoded external CLI deps.
    
  • Investigate a non-crashing account switch (upstream fix to the tg://…&acc= handler would remove the whole xdotool detour).

  • Include recent/top peers (contacts.getTopPeers) for non-contact recents.

  • Optional sync-on-open so the menu is always fresh.

Prior art

Supersedes two earlier personal experiments (tg-rofi, rofi-tg-switcher).

License

MIT OR Apache-2.0.

About

Rofi quick-switcher that jumps Telegram Desktop to any chat, group, channel, contact, or forum topic across all your accounts (Rust)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages