{"id":36740530,"url":"https://github.com/nktknshn/tgmount-ng","last_synced_at":"2026-01-12T12:28:25.687Z","repository":{"id":65654001,"uuid":"594492100","full_name":"nktknshn/tgmount-ng","owner":"nktknshn","description":null,"archived":false,"fork":false,"pushed_at":"2023-04-25T15:38:40.000Z","size":528,"stargazers_count":53,"open_issues_count":11,"forks_count":5,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-09-11T16:02:45.281Z","etag":null,"topics":["pyfuse3","python","telegram","telegram-client","telethon","vfs","virtual-file-system"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nktknshn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2023-01-28T18:05:58.000Z","updated_at":"2025-09-04T15:17:14.000Z","dependencies_parsed_at":"2023-02-19T16:16:04.412Z","dependency_job_id":null,"html_url":"https://github.com/nktknshn/tgmount-ng","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/nktknshn/tgmount-ng","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nktknshn%2Ftgmount-ng","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nktknshn%2Ftgmount-ng/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nktknshn%2Ftgmount-ng/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nktknshn%2Ftgmount-ng/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nktknshn","download_url":"https://codeload.github.com/nktknshn/tgmount-ng/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nktknshn%2Ftgmount-ng/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338976,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T12:22:26.515Z","status":"ssl_error","status_checked_at":"2026-01-12T12:22:10.856Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pyfuse3","python","telegram","telegram-client","telethon","vfs","virtual-file-system"],"created_at":"2026-01-12T12:28:25.090Z","updated_at":"2026-01-12T12:28:25.682Z","avatar_url":"https://github.com/nktknshn.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Overview\n\nCreates virtual file system with files posted on telegram.  \n\u003c!-- tgmount lets you to mount files uploaded to telegram as a virtual file system \nso access them like regular files from a cloud without downloading. This allows to use\nregular desktop media players to listen to music streaming it directly from telegram servers. Pictures and videos \n\nFeatures:\n* Flexible VFS structure configuration \n* ZIP archives as folders\n* Multiple files organization strategies\n--\u003e\n\n**VERY ALPHA SO FAR**\n\nTable of Contents\n=================\n* [Installation](#installation)\n* [Requirements](#requirements)\n* [Basic usage](#requirements)\n* [Mounting multiple entities](#mounting-multiple-entities)\n  * [Sample config](#sample-config)\n* [Client commands](#client-commands)\n  * [mount](#tgmount-mount)\n  * [mount config](#tgmount-mount-config)\n  * [list dialogs](#tgmount-list-dialogs)\n  * [list documents](#tgmount-list-documents)\n  * [download](#tgmount-download)\n* [Config file structure](#config-file-structure)\n* [Playing flac and mp3 from a zip archive](#playing-flac-and-mp3-from-a-zip-archive)\n* [Known bugs](#known-bugs)\n\n## Requirements\n- Linux\n- Python 3.10 (not sure about 3.9 and less)\n\n## Installation:\n\n```\npip install tgmount\n```\n\n## Basic usage\n\nTo obtain your API id follow [official manual](https://core.telegram.org/api/obtaining_api_id).  Running the program for the first time will require authentication.\n\n```\n$ export TGAPP=1234567:deadbeef0d04a3efe93e1af778773d6f0 TGSESSION=tgfs\n```\n\nTo mount a channel/chat/group\n\n```\ntgmount mount tgmounttestingchannel ~/mnt/tgmount1/\n```\n\nTo mount an entity that doesn't have a username you will need to get its id. \n```bash\ntgmount list dialogs | grep 'my friends private chat'\n```\n\nTo mount zip files as directories use `UnpackedZip` producer\n\n```\ntgmount mount tgmounttestingchannel ~/mnt/tgmount1/ --producer UnpackedZip\n```\n\nUse config file to create a more complex vfs structure  \n\n```\ntgmount mount tgmounttestingchannel ~/mnt/tgmount1/ --root-config examples/root_config.yaml\n```\n\n\n## Mounting multiple entities\n\nTo mount multiple entities use `mount-config` command\n\n```\ntgmount mount-config examples/config.yaml\n```\n\n### Sample config\n```yaml\n# can be overwritten by --mount-dir argument\nmount_dir: /home/horn/mnt/tgmount1\n\nclient:\n  session: tgfs\n  api_id: 123\n  api_hash: deadbeed121212121\n\n# \nmessage_sources:\n\n  ru2chmu:\n    entity: ru2chmu\n    updates: False\n    limit: 1000\n\n  friends:\n    entity: -388004022\n    limit: 1000\n    \ncaches:\n  memory1:\n    type: memory\n    capacity: 300MB\n    block_size: 128KB\n\nroot:\n  muzach:\n    # A document will not be mounted more than once when it appears in a \n    # different messages. `recursive` means this filter will also be applied \n    # down the folders tree\n    filter: { filter: OnlyUniqueDocs, recursive: True }\n    # Messages from `ru2chmu` will be used to produce content in the nested folders\n    source: { source: ru2chmu, recursive: True }\n    # creates subfolder named `music`\n    music:\n      filter: \n        # the directory will contain music and zip archives\n        Union: [MessageWithMusic, MessageWithZip]\n      # zip archives will be mounted as folders\n      producer: UnpackedZip\n      # using cache speeds up reading from the archives\n      cache: memory1\n    texts:\n      # messages with text \n      filter: MessageWithText\n      # this commands tgmount to treat messages with both document and text\n      # as text files\n      treat_as: MessageWithText\n\n  friends:\n    source: {source: friends, recursive: True}\n    music-by-senders:\n      producer:\n        # this producer creates a separate directory for every sender in the entity\n        BySender:\n          dir_structure:\n            # these directories will only contain music \n            filter: MessageWithMusic\n    liked-music:\n      # this directory will be containing all music with thumb up reaction\n      filter: \n        And: \n          - MessageWithMusic\n          - ByReaction: \n              reaction: 👍\n    images:\n      filter: \n        Union: [MessageWithCompressedPhoto, MessageWithDocumentImage]\n```\n\nMore about config structure read in [Config file structure](#config-file-structure)\n\n## Client commands\n\n\u003c!-- `tgmount auth` --\u003e\n\n### tgmount mount\n\n```\ntgmount mount [--filter FILTER] [--root-config ROOT_CONFIG]\n[--producer PRODUCER] [--offset-date OFFSET_DATE] [--offset-id OFFSET_ID] \n[--max-id MAX_ID] [--min-id MIN_ID] [--wait_time WAIT_TIME] [--limit LIMIT] \n[--reply-to REPLY_TO] [--from-user FROM_USER] [--reverse] [--mount-texts] [--no-updates] \n[--debug-fuse] [--min-tasks MIN_TASKS] entity mount-dir\n```\n\nDefine the structure of the mounted folder by one of these options\n```\n--producer PRODUCER\n--root-config ROOT_CONFIG\n```\n\nAvailable producers:\n\n```python\nPlainDir    # just a list of files (default)\nUnpackedZip # PlainDir but zips are mounted as folders\nBySender    # files grouped in folders by sender\nByForward   # forwarded files grouped by source entity\nByPerformer # music grouped by performers\nByReactions # files grouped by reaction\n```\n\nThe following arguments work as described in [TelegramClient.get_messages](https://docs.telethon.dev/en/stable/modules/client.html#telethon.client.messages.MessageMethods.get_messages). \n\n```\n--filter [FILTER]\n--offset-date OFFSET_DATE\n--offset-id OFFSET_ID\n--max-id MAX_ID\n--min-id MIN_ID\n--wait_time WAIT_TIME\n--limit LIMIT\n--reply-to REPLY_TO\n--from-user FROM_USER\n--reverse\n```\n\n\nAvailable [telegram filters](https://core.telegram.org/type/MessagesFilter):\n\n```python\nInputMessagesFilterDocument\nInputMessagesFilterPhotos\nInputMessagesFilterVideo\nInputMessagesFilterPhotoVideo\nInputMessagesFilterUrl\nInputMessagesFilterGif\nInputMessagesFilterVoice\nInputMessagesFilterMusic\nInputMessagesFilterRoundVoice\nInputMessagesFilterRoundVideo\nInputMessagesFilterMyMentions\n```\n\nUsing these filter speeds up fetching process but these filter cannot be composed.\n\nIf you don't need updates \n```\n--no-updates\n```\n\nIf you want to also to mount text messages as text files\n\n```\n--mount-texts\n```\n\nOther arguments\n```\n--debug-fuse\n--min-tasks MIN_TASKS\n```\n\n### tgmount mount-config\n\n```\ntgmount mount-config [--mount-dir MOUNT_DIR] CONFIG_FILE MOUNT_DIR\n```\n\n### tgmount list dialogs\n\n```\ntgmount list dialogs\n```\n\n### tgmount list documents\n\n```\ntgmount list documents [--filter FILTER] [--offset-date OFFSET_DATE] [--offset-id OFFSET_ID]\n[--max-id MAX_ID] [--min-id MIN_ID] [--wait_time WAIT_TIME] [--limit LIMIT] \n[--reply-to REPLY_TO] [--from-user FROM_USER] [--reverse] [--json]\n[--print-message] [--include-unsupported] [--only-unsupported] [--all-types]\n[--only-unique-docs] entity\n```\n\n```--print-message```\n\nInclude stringified message object in the output\n\n`--all-types`\n\nPrint all classes a message matches\n\n`--only-unique-docs`\n\nExclude repeating documents \n\n`--include-unsupported`\n\nInclude messages that are not supported for mounting\n\n`--only-unsupported`\n\nPrint only them\n\n`--json`\n\nPrint in json format\n\n### tgmount download\n\n```\ntgmount download [--output-dir OUTPUT_DIR] [--keep-filename] [--request-size REQUEST_SIZE] entity ids [ids ...]\n```\n\n`--keep-filename`\n\nKeep original filenames\n\n`--output-dir`\n\nDestination folder for files\n\n`--request_size`\n\nHow much data to fetch per request\n\n`entity`\n\nEntity to download from\n\n`ids`\n\nMessages ids\n\nExample:\n\n```\ntgmount download -O /tmp -R 256KB tgmounttestingchannel 532 11 51 18 \n```\n\nIm combination with `list documents`\n\n```bash\ntgmount download ru_python $(tgmount list documents ru_python --filter InputMessagesFilterDocument --limit 10 --json | jq  '.[]|.id') -O /tmp\n```\n\n\n## Config file structure\n\nConfig file has the following sections: \n- `client`\n- `message_sources`\n- `caches`\n- `root`\n\n`caches` section is optional.\n\n### Top level properties\n```yaml\n# optional. can be overwritten by --mount-dir argument \nmount_dir: ~/mnt/tgmount\n```\n### client\n\nContains settings for the telegram client\n\n```yaml\nclient:\n  # telethon session name \n  session: session_name\n\n  # telegram api credentials\n  api_id: int\n  api_hash: str\n\n  # optional field\n  request_size: 128KB\n\n  # optional field. Default: False\n  use_ipv6: True\n```\n\n### message_sources\nA message source defines a list of messages that will be used in vfs tree construction. Every message source is a separate [TelegramClient.get_messages](https://docs.telethon.dev/en/stable/modules/client.html#telethon.client.messages.MessageMethods.get_messages) request. Message source is also subscribed to events of posting, removing and editing messages in the entity it is sourced from. \n\n```yaml\nmessage_sources:\n  # key defines id of the message source to reference in the `root` section \n  source1: \n    # channel/group/chat id to fetch messages from\n    # string or int\n    entity: tgmounttestingchannel\n    \n    # all the following fields are optional\n\n    # whether to listen for updates. Default: true\n    updates: True\n\n    # Filter for message types. If not set all the messages types including text \n    # messages will be fetched\n    filter: MessageWithMusic\n\n    # limits the number of messages\n    limit: 1000\n\n    # format is `31/12/2023` or '31/12/2023 13:00'\n    offset_date: `31/12/2023`\n\n    offset_id: 0\n    min_id: 0\n    max_id: 0\n    wait_time: None\n    reply_to: int\n    from_user: str | int\n    reverse: False\n```\n\n### caches\n\nDefines cache storages for documents. Cached parts of a document will not be fetched twice. Usually this is not needed because OS file system does caching by itself. Cache is needed in couple with `UnpackedZip` producer since the OS file system cache is not applied in case of using this producer.\n\n```yaml\ncaches:\n  # the key defines cache id to be referenced in `root` section\n  cache1:\n    # currently only memory cache is supported \n    type: memory\n    # The size of the cache\n    capacity: 300MB\n    # optional block size, default: 128KB\n    block_size: 256KB\n```\n\n### root\n\nThis section defines the structure of the mounted folder.\n\n```yaml\nroot:\n  # optional. sets the message source for the current directory. If this is not\n  # set and there is no recursive filter has been defined before, the folder \n  # will not contain any files\n  source: source1\n  source: {source: source1}\n\n  # sets the message source for the current and for nested folders\n  source: {source: source1, recursive: True}\n  \n  # optional. sets a filter for the current folder. Default is no filter\n  filter: MessageWithMusic\n  filter: {filter: MessageWithMusic}\n\n  # sets a filter for the current folder and subfolders\n  filter: {filter: MessageWithMusic, recursive: True}\n\n  # sets a filter for the current folder and subfolders overwriting another \n  # recursive filter if any \n  filter: {filter: MessageWithMusic, overwright: True, recursive: True}\n\n  # the following combines multiple filters. Only messages that match every filter\n  # in the list will pass. The filter below allows all documents that\n  # that are not video, photo or audio and not a zip file \n  filter: \n    - MessageWithOtherDocument\n    - Not:\n      - ByExtension: .zip\n\n  # in one line\n  filter: {filter: [MessageWithOtherDocument, Not: {ByExtension: .zip}], overwright: True, recursive: True}\n\n  # defines a producer that controls the content of the folder. \n  # Default is PlainDir\n  producer: BySender\n\n  # producer may have properties\n  producer: \n    BySender:\n      dir_structure:\n        music: \n          filter: MessageWithMusic\n        voices: \n          filter: MessageWithVoice\n      use_get_sender: true\n\n  # sets a cache for the current folder\n  # referencing a cache defined in `caches` folder \n  cache: memory1\n\n  # dynamically creates a cache to use in this folder\n  cache: \n    type: memory\n    capacity: 300MB\n\n  # optional. wrapper that modifies the resulting content of the folder \n  wrapper: ExcludeEmptyDirs\n\n  # optional. Defines the priority of how to classify a message if multiple classes\n  # match its type. E.g. a message with both a document and a text message  \n  treat_as: MessageWithText\n\n  # to define subfolders\n  documents:\n    # 'documents' folder will only contain the two following subfolders \n    docs_from_source1:\n      source: source1\n      filter: MessageWithDocument\n    docs_from_source2:  \n      source: source2\n      filter: MessageWithDocument\n```\n\n\n#### source\n\nMessage source is a list of messages which is used to produce a directory content. Message source is initialized from get_messages() request and is updated by events of posting message, removing message and editing message in the corresponding entity. \n\nProducer is subscribed to a message source and takes care of the directory it is responsible for. It manages it by adding and removing files and subfolders.\n\nThe content of a folder is defined by a combination of properties `source`, `filter`, `producer` and `treat_as`. \n\nThis will create a tree of empty folders\n```yaml\nroot:\n  everything:\n  photos:\n  texts:\n  round-and-voice:\n    rounds:\n    voices:\n```\nThe config will result into\n```\n/everything\n/photos\n/texts\n/round-and-voice\n/round-and-voice/rounds\n/round-and-voice/voices\n```\nTo fill the directories with files we need to specify a source for every folder that is supposed to contain files\n```yaml\nroot:\n  everything:\n    source: source1 \n  photos:\n    source: source1\n  texts:\n    source: source1\n  round-and-voice:\n    rounds:\n      source: source1\n    voices:\n      source: source1\n```\n\nIn result every directory that has `source` property will contain all the files from the specified source.\n\nLet's add filters \n\n```yaml\nroot:\n  everything:\n    # don't need filter here\n    source: source1\n  photos:\n    source: source1\n    filter: MessageWithCompressedPhoto\n  texts:\n    source: source1\n    filter: MessageWithText\n    treats_as: MessageWithText\n  round-and-voice:\n    rounds:\n      source: source1\n      filter: MessageWithKruzhochek\n    voices:\n      source: source1\n      filter: MessageWithVoice\n```\nAs soon as the only source used in the structure is \"source1\" we can get rid of repeating it by using `recursive` property of `source`.\n\n```yaml\nroot:\n  source: {source: source1, recursive: True}\n  everything:\n    filter: All\n  photos:\n    filter: MessageWithCompressedPhoto\n  texts:\n    filter: MessageWithText\n    treats_as: MessageWithText\n  round-and-voice:\n    rounds:\n      filter: MessageWithKruzhochek\n    voices:\n      filter: MessageWithVoice\n```\n\nNote that\n1. The root itself will not contain any files because source with `recursive` flag doesn't trigger file producing\n2. We had to specify `filter` in \"everything\" to trigger file producer. For the same effect we could have specified a producer instead.\n```yaml\neverything:\n  # triggers producing from the recursive source\n  producer: PlainDir\n```  \n\nThe complete rules:\n\nA folder will be produced with content from a message source in cases when:\n1. source is specified and it's not recursive\n2. recursive source is in the context and `filter` property specified and it's not recursive\n3. recursive source is in the context and `producer` prop is specified\n\n#### filter\n\nBy message type:\n\n```python\nMessageWithDocument # Message with a document attached (message with compressed\n#  image doesn't match) \nMessageWithCompressedPhoto # with a compressed image (photo)\nMessageDownloadable # `MessageWithDocument` or `MessageWithCompressedPhoto`\nMessageWithAnimated # stickers, gifs\nMessageWithAudio # voices and music\nMessageWithVoice # voice\nMessageWithKruzhochek # round video\nMessageWithDocumentImage # uncompressed image\nMessageWithFilename # document with a filename attribute\nMessageWithMusic # music\nMessageWithVideo # round video, video documents, stickers, gifs\nMessageWithVideoFile # video documents\nMessageWithSticker # sticker\nMessageWithOtherDocument # Any document that doesn't fall in the previous categories\nMessageWithZip # zip file\nMessageWithText # message with text message\nMessageWithoutDocument # message with no document and no photo\nMessageWithReactions # message with reactions\nMessageForwarded # forwarded message\n\n# Telegram filters\nInputMessagesFilterPhotos     # MessageWithCompressedPhoto\nInputMessagesFilterVideo      # MessageWithVideo\nInputMessagesFilterPhotoVideo # MessageWithCompressedPhoto | MessageWithVideo\nInputMessagesFilterDocument   # MessageWithOtherDocument | MessageWithDocumentImage\nInputMessagesFilterGif        # MessageWithAnimated\nInputMessagesFilterVoice      # MessageWithVoice\nInputMessagesFilterMusic      # MessageWithMusic\nInputMessagesFilterRoundVideo # MessageWithKruzhochek\nInputMessagesFilterRoundVoice # MessageWithKruzhochek | MessageWithVoice\n```\n\nOther filters\n\n```yaml\n# Filter wrapper to reverse a filter. \nNot: MessageWithReactions\n\n# Combines multiple filters. If any matches\nUnion: \n  - MessageWithDocumentImage\n  - MessageWithCompressedPhoto\n\n# Combines multiple filters. If every matches\nAnd:\n  - MessageForwarded\n  - MessageWithVideo\n\n# same as\nfilter: [MessageForwarded, MessageWithVideo]\n\n# takes first `count` messages\nFirst:\n  count: 10\n\n# takes last `count` messages\nLast:\n  count: 10\n\n# Filter by a filename extension\nByExtension: .zip\n\n# will only leave unique docs\nOnlyUniqueDocs:\n  # optional. Control which document, first appeared or last appeared, will stay.\n  # default: first\n  picker: last \n  picker: first \n\n# passthrough filter. Used to trigger tgmount to produce content in the folder\n# or to reset recursive filter\nAll\n\n# sequentially filters messages. E.g. last 10 unique documents \nSeq:\n  - MessageWithDocument\n  - OnlyUniqueDocs\n  - Last: 10\n\n# matches reactions\nByReaction:\n  reaction: 👍\n  # optional. default: 1\n  minimum: 5\n```\n\n#### producer\n\n```python\nPlainDir\nBySender\nByForward\nByPerformer\nByReactions\nSysInfo\nUnpackedZip\n```\n\n## Playing flac and mp3 from a zip archive\n1. Seeking in files which are stored in a zip archive only works by reading the \noffset bytes.  \n2. id3v1 tags are stored in the end of a media file :)\nhttps://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_id3v1.py#L34\n\nAnd most of the players try to read it. So just adding a mp3 or flac\nto a player will fetch the whole file from the telegram cloud.\n\nIn current moment this is solved by custom read function for mp3 and flac files \nin archives. The `read` call returns 4096 zero bytes when\n  1. less than `max_total_read = 128KB` bytes has been read from the file so far\n  2. `file_size - offset \u003c distance_to_file_end = 16KB`\n  3. `size == 4096` (usually players read this amount looking for id3v1 (requires \n  further investigation to find a less hacky way))\n\n  See `FileContentZipFixingId3v1` class\n\nTo disable this behavior use `--no-fix-id3v1` argument with `mount` command. \nIn case of mounting a config set `fix_id3v1` property of `UnpackedZip` to False:\n```yaml\nproducer: {UnpackedZip: {fix_id3v1: False}}\n```\n\n## Known bugs\n- No updates received during reconnection\n- Combination of `--filter`, `--offset-date` and `--reverse` always returns empty result","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnktknshn%2Ftgmount-ng","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnktknshn%2Ftgmount-ng","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnktknshn%2Ftgmount-ng/lists"}