feat: add hosting and qr-code skills

This commit is contained in:
Moritz Graf
2026-09-16 08:59:54 +02:00
parent 5376ddbc20
commit bb31002e19
6 changed files with 403 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
---
name: qr-code
description: Generate a branded Haumdaucher QR code for any URL or text string and send the resulting image into chat. Usable by all agents.
---
# QR Code Generator 📱
Creates high-redundancy, branded QR codes with the **Haumdaucher logo** centered on a clean white background.
## When to Use
- Whenever you share a link (`https://...`), website, recipe, document, Wi-Fi configuration, or arbitrary text and want to offer a scannable QR code.
- Whenever the user accepts an `ask_user` prompt to generate a QR code for a hosted artifact.
- Whenever the user explicitly asks for a QR code ("Erstelle einen QR-Code für ...").
## Execution
Run the bundled Python script via `uv run`:
```bash
uv run /home/node/mop-skills/mop-skills/skills/qr-code/scripts/generate_qr.py "<URL-or-Text>"
```
### Script Arguments:
- `<text>`: The URL or string to encode (mandatory).
- `--output <path>`: Optional target file path (defaults to `/tmp/qr_<hash>.png`).
- `--logo <path>`: Optional custom logo PNG (defaults to bundled Haumdaucher logo).
## Output & Telegram Delivery
The script outputs:
```
MEDIA:/tmp/qr_c9e87fb7.png
```
When returning your reply to the user, include the `MEDIA:<path>` line at the very beginning or end of your message. OpenClaw's Telegram integration will automatically upload the image directly into the chat:
```markdown
Hier ist dein QR-Code für den Link:
MEDIA:/tmp/qr_c9e87fb7.png
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
# /// script
# dependencies = [
# "qrcode[pil]>=7.4",
# "pillow>=10.0.0",
# ]
# ///
"""
Haumdaucher QR Code Generator with Centered Logo.
Encodes any URL or string into a high-error-correction QR code (ERROR_CORRECT_H),
embeds the Haumdaucher logo in the center with a clean white rounded badge,
and outputs MEDIA:<path> for direct Telegram inline photo delivery.
"""
import argparse
import hashlib
import os
import sys
from pathlib import Path
from PIL import Image, ImageDraw
import qrcode
from qrcode.constants import ERROR_CORRECT_H
DEFAULT_LOGO_PATH = Path(__file__).resolve().parent.parent / "assets" / "haumdaucher_logo.png"
def generate_qr_code(text: str, output_path: str = None, logo_path: str = None) -> str:
if not text:
raise ValueError("Text/URL to encode cannot be empty.")
if not output_path:
text_hash = hashlib.sha256(text.encode("utf-8")).hexdigest()[:8]
output_path = f"/tmp/qr_{text_hash}.png"
# Resolve logo path
logo_file = Path(logo_path) if logo_path else DEFAULT_LOGO_PATH
if not logo_file.exists():
# Fallback to current script dir or assets if relative path differs
logo_file = DEFAULT_LOGO_PATH
# Initialize QR code generator with High error correction (30% redundancy)
qr = qrcode.QRCode(
version=None, # Auto-detect best version
error_correction=ERROR_CORRECT_H,
box_size=12,
border=4,
)
qr.add_data(text)
qr.make(fit=True)
# Generate base QR code image (RGBA for clean compositing)
qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGBA")
qr_width, qr_height = qr_img.size
# Overlay logo if available
if logo_file.exists():
try:
logo = Image.open(logo_file).convert("RGBA")
# Logo should occupy roughly 22% of the QR code dimensions to ensure readability
max_logo_size = int(qr_width * 0.23)
# Maintain aspect ratio
logo_w, logo_h = logo.size
scale = min(max_logo_size / logo_w, max_logo_size / logo_h)
new_w = int(logo_w * scale)
new_h = int(logo_h * scale)
logo = logo.resize((new_w, new_h), Image.Resampling.LANCZOS)
# Create a white rounded badge behind the logo for contrast and module separation
badge_pad = 8
badge_w = new_w + badge_pad * 2
badge_h = new_h + badge_pad * 2
badge = Image.new("RGBA", (badge_w, badge_h), (0, 0, 0, 0))
draw = ImageDraw.Draw(badge)
radius = 12
draw.rounded_rectangle(
[(0, 0), (badge_w - 1, badge_h - 1)],
radius=radius,
fill=(255, 255, 255, 255),
outline=(220, 220, 220, 255),
width=1,
)
# Paste logo onto the white badge
badge.paste(logo, (badge_pad, badge_pad), mask=logo)
# Paste badge into the center of the QR code
pos_x = (qr_width - badge_w) // 2
pos_y = (qr_height - badge_h) // 2
qr_img.paste(badge, (pos_x, pos_y), mask=badge)
except Exception as e:
sys.stderr.write(f"Warning: could not overlay logo: {e}\n")
# Ensure output directory exists and save
out_file = Path(output_path)
out_file.parent.mkdir(parents=True, exist_ok=True)
qr_img.save(str(out_file), format="PNG")
return str(out_file)
def main():
parser = argparse.ArgumentParser(description="Generate a Haumdaucher-branded QR code.")
parser.add_argument("text", nargs="?", help="Text or URL to encode into the QR code")
parser.add_argument("--text", "-t", dest="text_opt", help="Text or URL to encode")
parser.add_argument("--output", "-o", help="Output file path (default: /tmp/qr_<hash>.png)")
parser.add_argument("--logo", "-l", help="Custom path to center logo PNG")
parser.add_argument("--quiet", "-q", action="store_true", help="Only output MEDIA tag")
args = parser.parse_args()
raw_text = args.text or args.text_opt
if not raw_text:
parser.error("You must provide text or a URL to encode.")
out_path = generate_qr_code(raw_text, output_path=args.output, logo_path=args.logo)
# Standard OpenClaw / Telegram media dispatch tag
print(f"MEDIA:{out_path}")
if not args.quiet:
sys.stderr.write(f"QR code successfully generated for: {raw_text}\nSaved to: {out_path}\n")
if __name__ == "__main__":
main()