Sales & Dungeons (S&D)

repository·master·Indexed 20 days ago

https://github.com/bigjk/snd

A utility for Dungeons & Dragons and other PnP games that uses ESC/POS thermal printers to create physical handouts like magic items, spells, and character sheets. It features a template system using HTML/CSS and Nunjucks, a headless Docker version, an Android host, and a programmatic SDK for remote management and print triggering via a custom mini-RPC framework.

Tokens
12.1K
Snippets
51
Records
59
Agent score
70%

What's inside snd

  1. Interact with Sales & Dungeons via the SDK

    master

    Sales & Dungeons (S&D) is architected as a split application consisting of a backend (managing the database, printing, etc.) and a UI. Because the backend and frontend communicate via HTTP, you can interact with S&D remotely or programmatically.

    Common use cases for the SDK include:

    • Remotely managing templates, generators, and data sources (create, delete, edit).
    • Scripting data imports from external sources into S&D.
    • Connecting external applications to S&D data.
    • Triggering print jobs.
    • Building custom user interfaces.
  2. How the S&D communication protocol works

    master

    S&D uses a custom mini-RPC framework called nra for communication. All interactions are performed via HTTP POST requests.

    Request Format:

    • Method: Always POST.
    • Endpoint: http://127.0.0.1:7123/api/FUNCTION_NAME (replace FUNCTION_NAME with the specific function you wish to call).
    • Body: A JSON-encoded array of arguments. For example, ["arg1", "arg2", 3, { "hello": "world" }].

    Discovery:

    • To see a list of all available functions, send a request to: http://127.0.0.1:7123/api/functions.
    # Example: Calling a function named 'printJob' with arguments
    POST /api/printJob HTTP/1.1
    Host: 127.0.0.1:7123
    Content-Type: application/json
    
    ["job_id_123", true]
  3. How Sales & Dungeons processes templates and printing

    master

    The printing workflow follows these steps:

    1. Templates: Created using HTML/CSS combined with the Nunjucks templating language. This allows for complex layouts and the use of external frameworks like Fontawesome.
    2. Rendered HTML: Nunjucks processes your data and templates to produce a final HTML document.
    3. Rendered Image: The HTML is converted into an image using Chrome via the Chrome Debug Protocol. This ensures high fidelity for modern CSS features.
    4. ESC/POS Commands: The rendered image is converted into the printer's native "draw image" command.
    5. Printer: The final command is sent to the thermal printer for output.
  4. Printer Requirements for Sales & Dungeons

    master

    Sales & Dungeons requires a thermal printer that supports ESC/POS (Epson Standard Code) control codes.

    How to verify compatibility:

    • Check if the printer manual or description mentions ESC/POS or Epson.
    • Most inexpensive Chinese thermal printers (from Amazon/AliExpress) support it.
    • Most Epson thermal printers support it.
    • Many older Serial printers (e.g., Metapace T-1) support it.

    For a list of specific tested models and settings, refer to the project's Printer-Settings wiki.

  5. Install the Android NDK for gomobile

    master

    The gomobile bind command requires the Android NDK. You can install the specific version required by the project using the sdkmanager tool. If you haven't installed the Android SDK Command-line Tools, install them via Android Studio first.

    To install the default NDK version:

    ~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.3.11579264"

    Alternatively, you can instruct the build_android.sh script to handle the NDK installation for you:

    SND_ANDROID_INSTALL_NDK=true ./build_android.sh
    ~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager --install "ndk;26.3.11579264"
  6. Install Sales & Dungeons on macOS

    master

    The macOS bundles are currently unsigned. If you encounter issues opening the application, follow these steps:

    Unverified Developer Error

    If macOS prevents opening because the app is from an unverified developer, allow it via the Privacy & Security settings in System Settings.

    'App is Damaged' Error (M1/M2/etc.)

    On Apple Silicon Macs, the app may be reported as damaged. To resolve this, move the application to your /Applications folder and run the following command in your terminal to remove the quarantine attribute:

    xattr -d com.apple.quarantine "/Applications/Sales & Dungeons.app/"
  7. Run Sales & Dungeons via Docker

    master

    A headless version of Sales & Dungeons (using LibUSB) is available as a Docker container. This allows you to run the service and access it via a web browser.

    Manual Docker Run

    1. Pull the image: docker pull ghcr.io/bigjk/snd:master
    2. Run the container with the following parameters:
      • -p 7123:7123: Maps the web interface port.
      • --device=/dev/bus/usb: Replace this with the actual device path of your USB/Serial printer.
      • --group-add uucp: Replace uucp with the group (or GID) allowed to read your device file. You can find the correct group using $(stat -c "%g" /dev/bus/usb).
      • -v /some/place/to/persist:/app/userdata: Replace /some/place/to/persist with a local directory to persist user data.
    3. Access the interface at http://127.0.0.1:7123.

    Docker Compose Configuration

    You can use the following docker-compose.yml structure:

    version: "3"
    services:
      snd:
        image: ghcr.io/bigjk/snd:master
        ports:
          - "7123:7123"
        devices:
          - "/dev/bus/usb"
        group_add:
          - uucp
        volumes:
          - "/some/place/to/persist:/app/userdata"
  8. Build the Android Host for Sales & Dungeons

    master

    To build the Android Host, you must first build the frontend, then bind the Go mobile library, and finally assemble the Android app using Gradle.

    1. Build the Frontend

    Navigate to the frontend directory and use bun to build the assets:

    cd frontend
    bun run build

    2. Build the gomobile AAR

    From the repository root, use gomobile bind to create the .aar file. This requires the Android NDK to be installed.

    go get -tool golang.org/x/mobile/cmd/gobind
    gomobile bind -target=android -androidapi 26 -o android/app/libs/sndmobile.aar ./mobile

    3. Assemble the Android App

    Navigate to the android directory and use Gradle to build the debug APK:

    cd android
    ./gradlew assembleDebug

    Automated Build

    You can run the entire process (frontend build, gomobile binding, and Gradle task) in one step from the repository root using the provided build script:

    ./build_android.sh
    ./build_android.sh
  9. Configure Android Build Environment Variables

    master

    The build_android.sh script supports several environment variables to customize the build process:

    • SND_ANDROID_API: Overrides the default -androidapi 26 passed to gomobile bind. Use this if the app's minSdk changes.
    • SND_ANDROID_INSTALL_NDK: Set to true to allow the build script to install the NDK automatically.
    • SND_GRADLE: Specifies a custom path to a Gradle binary if the project does not have a Gradle wrapper or you wish to use a specific version.

    Example of using a custom Gradle path:

    SND_GRADLE=/path/to/gradle ./build_android.sh
  10. Define Schema types and structures

    master

    The schema system uses SchemaType to define the primitive types allowed in a node and SchemaNode to represent individual data points within a hierarchy. A SchemaRoot acts as the container for the top-level nodes.

    SchemaType

    Supported types:

    • 'string'
    • 'number'
    • 'boolean'
    • 'array'
    • 'object'

    SchemaNode

    Key properties:

    • type: The SchemaType of the node.
    • elemType: (Optional) The SchemaType of elements if the node is an array.
    • inputType: A string representing the UI input type (e.g., 'Text', 'Number', 'Checkbox', 'Image', 'Array', 'Object').
    • key: The identifier for the node.
    • children: (Optional) An array of SchemaNode for object or array of object types.
    • default: The default value for the node.
  11. Define and identify SessionGrid structures

    master

    A SessionGrid is a collection of elements used to define a sequence of actions or templates. It consists of a name and an array of elements. Elements can be individual GridElement types or a GridLinearExecution block which allows for repeating a sequence of elements.

    GridElement Types

    • GridTemplateElement: Uses a templateId to reference a specific template. Can optionally include a dataSourceId, entryId, or configName.
    • GridGeneratorElement: Uses a generatorId to trigger a generator. Can optionally include configName and aiEnabled.
    • GridPrinterCommandElement: Executes a physical printer command. The command must be one of 'cut', 'drawer1', or 'drawer2'.

    Execution Patterns

    • GridLinearExecution: A container that allows you to group elements and optionally specify a repeat count to execute that group multiple times.
    const session: SessionGrid = {
      name: "Example Session",
      elements: [
        {
          templateId: "template-123",
          color: "#ff0000"
        },
        {
          repeat: 3,
          elements: [
            { command: "cut" },
            { generatorId: "gen-abc", aiEnabled: true }
          ]
        }
      ]
    };
  12. Use the SndAPI Python SDK

    master

    The SndAPI class provides a programmatic interface to the Sales & Dungeon API. It is automatically generated from the API definition.

    Initialization

    To use the SDK, initialize the SndAPI class with the base_url of your API instance. A common default is http://127.0.0.1:7123.

    API Methods

    Methods are generated based on the API endpoints. Each method performs a POST request to an api/ endpoint and returns the JSON response.

    Data Types

    The SDK maps Go/SND types to Python types as follows:

    • string $\rightarrow$ str
    • int $\rightarrow$ int
    • float $\rightarrow$ float
    • bool $\rightarrow$ bool
    • map or interface {} $\rightarrow$ dict
    • []T (slices) $\rightarrow$ list of T
    • snd.TypeName $\rightarrow$ dict [snd type type_name]
    import requests
    
    # Example of how the generated SDK is structured
    class SndAPI:
        def __init__(self, base_url):
            """
            Initialize the Sales & Dungeon API class with the base URL of the API.
    
            Parameters:
            - base_url (str): The base URL of the API. Most likely "http://127.0.0.1:7123"
            """
            self.base_url = base_url
    
        def _make_request(self, endpoint, method='GET', params=None):
            url = f"{self.base_url}/{endpoint}"
            if method == 'GET':
                response = requests.get(url, params=params)
            elif method == 'POST':
                response = requests.post(url, json=params)
            elif method == 'DELETE':
                response = requests.delete(url, json=params)
            else:
                raise ValueError(f"Unsupported HTTP method: {method}")
    
            if response.status_code == 200:
                return response.json()
            else:
                response.raise_for_status()
    
        # Example of a generated method
        def example_action(self, arg0, arg1):
            """
            Perform an action using the example_action API endpoint.
    
            Parameters:
            - arg0 (str): parameter
            - arg1 (int): parameter
            """
            endpoint = "api/example_action"
            return self._make_request(endpoint, method='POST', params=[arg0, arg1])