## freeqsay — cowsay with your FreeqWorld face. ## ## freeqsay ## echo "hi" | freeqsay ## freeqsay did:plc:… "offline, no resolve" ## ## As a library, this module re-exports the whole port: `freeqsay.say` (balloon ## + face), `avatar` (traits and the 16×24 sprite), `ansi` (truecolor ## half-block rendering), `png` (RGBA → PNG), `resolve` (handle → DID), ## `balloon`, `sha256`. import freeqsay/[ansi, avatar, balloon, png, resolve, say, sha256] export ansi, avatar, balloon, png, resolve, say, sha256 when isMainModule: import std/[os, strutils] const usageText = """ freeqsay — cowsay with a FreeqWorld character Usage: freeqsay freeqsay # message from stdin echo "hello" | freeqsay Resolves a Bluesky handle to a DID, derives the deterministic avatar, and prints a cowsay balloon above the character. A raw did:… works too. Options: -W, --width Balloon wrap width (default 40) --scale Character pixel scale (default 2) --facing south | north | east | west (default south) --think Thought bubble instead of speech -o, --output Write to file (else stdout) -h, --help Show help Examples: freeqsay jay.bsky.team hello from the timeline freeqsay @pfrazee.com "ships free software" freeqsay someone.bsky.social -W 20 short lines work echo "piped in" | freeqsay jay.bsky.team """ proc usage(code = 1) = stderr.writeLine usageText quit(code) proc positiveInt(s: string): int = try: result = parseInt(s.strip()) except ValueError: usage() if result < 1: usage() type Args = object handle, output: string messageParts: seq[string] width, scale: int facing: Facing think: bool proc parseArgs(argv: seq[string]): Args = result = Args(width: 40, scale: 2, facing: fSouth) var i = 0 while i < argv.len: let a = argv[i] proc value(): string = # A flag with no value left is a usage error, not an index crash. if i + 1 >= argv.len: usage() inc i argv[i] case a of "-h", "--help": usage(0) of "--think": result.think = true of "-W", "--width": result.width = positiveInt(value()) of "--scale": result.scale = positiveInt(value()) of "--facing": let f = value() if f notin ["south", "north", "east", "west"]: usage() result.facing = parseEnum[Facing](f) of "-o", "--output": result.output = value() else: if a.startsWith("-"): stderr.writeLine "unknown flag: " & a usage() elif result.handle.len == 0: result.handle = a else: result.messageParts.add a inc i proc main() = let args = parseArgs(commandLineParams()) if args.handle.len == 0: usage() var message = args.messageParts.join(" ").strip() if message.len == 0: message = readAll(stdin).strip() if message.len == 0: usage() let result = freeqsay(args.handle, message, width = args.width, think = args.think, scale = args.scale, facing = args.facing) let text = result.output & "\n" if args.output.len > 0: writeFile(args.output, text) else: stdout.write text stdout.flushFile() try: main() except CatchableError as e: stderr.writeLine e.msg quit(1)