Skip to content

Distributing Modules

You wrote a module. It's beautiful. It does something useful (or useless, but fun). And now you want to share it with the world.

There are three ways to distribute your modules.

This is the civilized way. Like pip, npm, but for MXUserBot.

How It Works

You upload modules to GitHub/GitLab/any git host, generate index.json and tell users to add your repository. That's it.

Step by Step

  1. Create the structure:

    your-repo/                    ← your git repo name
    ├── modules/                  ← modules FOLDER (always named this way)
    │   ├── wikipedia.py          ← single file module
    │   ├── calculator.py         ← another single file
    │   └── myepicmodule/         ← FOLDER = ZIP module [multiple files]
    │       ├── __init__.py
    │       └── utils.py
    └── index.json                ← generated by the script below
    

  2. Change BASE_URL in the script to your own:

    Format:

    https://raw.githubusercontent.com/YOUR_USERNAME/REPO_NAME/main/modules
    

    Example: if your repo is https://github.com/miku1337/my-modules, then:

    BASE_URL = "https://raw.githubusercontent.com/miku1337/my-modules/main/modules"
    

  3. Run the script — it will create index.json and zip folder-based modules

  4. Push everything to GitHub

  5. Tell users to add:

    .addrepo https://raw.githubusercontent.com/YOUR_USERNAME/REPO_NAME/main
    

Script to Generate index.json

Click to see the code (spoiler)
import ast
import io
import json
import zipfile
from pathlib import Path

 BASE_URL = "https://raw.githubusercontent.com/YOUR_USERNAME/REPO_NAME/main/modules"  # ← CHANGE TO YOURS!

SKIP_DIRS = {"__pycache__"}
SKIP_EXTS = {".pyc", ".pyo"}
SKIP_FILES = {"__pycache__"}


def extract_value(node):
    try:
        return ast.literal_eval(node)
    except Exception:
        return None


def extract_meta(source: str, default_url: str) -> dict | None:
    try:
        tree = ast.parse(source)
    except (SyntaxError, UnicodeDecodeError):
        return None

    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef) and node.name == "Meta":
            meta = {
                "url": default_url,
                "author": "Unknown",
                "dependencies": [],
                "tags": [],
            }
            mapping = {
                "name": "name",
                "description": "description",
                "version": "version",
                "dependencies": "dependencies",
                "tags": "tags",
                "author": "author",
            }
            for item in node.body:
                if isinstance(item, ast.Assign):
                    for target in item.targets:
                        if isinstance(target, ast.Name) and target.id in mapping:
                            val = extract_value(item.value)
                            if val is not None:
                                meta[mapping[target.id]] = val
            return meta
    return None


def package_folder(folder: Path) -> bytes:
    buf = io.BytesIO()
    with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
        for path in folder.rglob("*"):
            if path.is_dir():
                continue
            rel = path.relative_to(folder.parent)
            if path.name in SKIP_FILES or path.suffix in SKIP_EXTS:
                continue
            if any(part in SKIP_DIRS for part in path.relative_to(folder).parts):
                continue
            zf.write(path, str(rel))
    return buf.getvalue()


def generate_index():
    folder_name = BASE_URL.rstrip("/").split("/")[-1]
    modules_dir = Path(folder_name)

    index_data = {}

    if not modules_dir.exists() or not modules_dir.is_dir():
        print(f"Folder {modules_dir} not found")
        return

    for file in sorted(modules_dir.glob("*.py")):
        if file.name.startswith("_"):
            continue

        meta = extract_meta(file.read_text("utf-8"), f"{BASE_URL.rstrip('/')}/{file.name}")
        if meta:
            index_data[file.stem] = meta
            print(f"Indexed: {file.stem}")
        else:
            print(f"Skipped (no Meta): {file.name}")

    for folder in sorted(modules_dir.iterdir()):
        if not folder.is_dir() or folder.name.startswith("_") or folder.name in SKIP_DIRS:
            continue

        init_file = folder / "__init__.py"
        if not init_file.exists():
            print(f"Skipped folder (no __init__.py): {folder.name}/")
            continue

        zip_name = f"{folder.name}.zip"
        zip_path = modules_dir / zip_name

        source = init_file.read_text("utf-8")
        meta = extract_meta(source, f"{BASE_URL.rstrip('/')}/{zip_name}")
        if not meta:
            print(f"Skipped folder (no Meta): {folder.name}/")
            continue

        data = package_folder(folder)
        zip_path.write_bytes(data)
        index_data[folder.name] = meta
        print(f"Packed & indexed: {folder.name}/ → {zip_name}")

    output_file = Path("index.json")
    output_file.write_text(
        json.dumps(index_data, indent=2, ensure_ascii=False),
        encoding="utf-8",
    )
    print(f"\nindex.json written ({len(index_data)} entries)")


if __name__ == "__main__":
    generate_index()

How Users Will Install Your Modules

After .addrepo <url> modules are available as:

.mdl USER/module_name

For example:

.mdl miku/wikipedia

Web Interface

Easier through the web: modulesstore → search.

About "community" repos

By default, your repository is marked as community — an untrusted source. The user will see a warning on install. Nothing personal, just security.

Want to be in the official repo?

If you want your module in the main MXUserBot repository:

  1. Clone mx-modules
  2. Add your module to modules/
  3. Run script.py to update index.json
  4. Submit a PR (Pull Request)

Method 2: Share a File

For quick development or if you're lazy.

How It Works:

  1. You drop a wikipedia.py file in the room
  2. The user replies to it with .mdl dev
  3. The bot shows a warning: "Untrusted source, are you sure?"
  4. User confirms — module installed

Perfect for testing or when you can't be bothered with a repository.


Same as method 2, but instead of a file — a direct raw link:

.mdl dev https://raw.githubusercontent.com/USER/repo/main/module.py

The user will get the same untrusted source warning.


Quick Cheat Sheet

Method When to use Trust level
Repository Publishing for others community (warning)
File Development, testing dev (warning)
Link Quick share dev (warning)

Tip: Use a repository. It's clean, civilized, and lets users update modules via .mdl update.