nandi/freeqsay-nimpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/freeqsay-nim.git
git clone ssh://git@rickub.com/nandi/freeqsay-nim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

freeqsay.nim · 114 lines · 3.4 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
## freeqsay — cowsay with your FreeqWorld face.
##
##   freeqsay <handle> <message...>
##   echo "hi" | freeqsay <handle>
##   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 <handle> <message...>
  freeqsay <handle>                 # message from stdin
  echo "hello" | freeqsay <handle>

  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 <n>      Balloon wrap width (default 40)
  --scale <n>          Character pixel scale (default 2)
  --facing <dir>       south | north | east | west (default south)
  --think              Thought bubble instead of speech
  -o, --output <path>  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)