aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2025-03-07 19:13:17 -0500
committerPaul Duncan <pabs@pablotron.org>2025-03-07 19:13:17 -0500
commit75539a6e72a58a3ec2600a1aa902790660e09791 (patch)
treee15f4db3f8ce3ba5c356793887328273de829b9b
downloadjim-bot-75539a6e72a58a3ec2600a1aa902790660e09791.tar.xz
jim-bot-75539a6e72a58a3ec2600a1aa902790660e09791.zip
initial commitr20250307
-rw-r--r--.gitignore1
-rw-r--r--Dockerfile16
-rw-r--r--Makefile8
-rw-r--r--bot/bot.go546
-rw-r--r--bot/bot_test.go104
-rw-r--r--go.mod3
-rw-r--r--main.go26
7 files changed, 704 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d01e8f8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+./jim-bot
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a9abbab
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+# build stage
+FROM docker.io/golang:1.24.1-alpine AS build
+COPY . /src
+WORKDIR /src
+
+# cache /go between builds to cache packages and improve build speed
+RUN --mount=type=cache,target=/go ["go", "build", "-trimpath", "-ldflags=-s -w"]
+
+# run stage
+#
+# this stage used to use "FROM scratch", but we need ca-certificates in
+# order to connect to the NWS API.
+FROM gcr.io/distroless/static
+ENV TZ "America/New_York"
+COPY --from=build /src/jim-bot /jim-bot
+CMD ["/jim-bot"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..af99727
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+
+@PHONY=all app
+
+all:
+ go build -trimpath -ldflags='-s -w'
+
+clean:
+ go clean
diff --git a/bot/bot.go b/bot/bot.go
new file mode 100644
index 0000000..233a865
--- /dev/null
+++ b/bot/bot.go
@@ -0,0 +1,546 @@
+// Port of jim-bot from C to go.
+package bot
+
+import (
+ "math/rand/v2"
+ "strings"
+)
+
+// adverb word list
+var adverbs = []string {
+ "appropriately",
+ "assertively",
+ "authoritatively",
+ "collaboratively",
+ "compellingly",
+ "competently",
+ "completely",
+ "continually",
+ "conveniently",
+ "credibly",
+ "distinctively",
+ "dramatically",
+ "dynamically",
+ "efficiently",
+ "energistically",
+ "enthusiastically",
+ "fungibly",
+ "globally",
+ "holisticly",
+ "interactively",
+ "intrinsicly",
+ "monotonectally",
+ "objectively",
+ "phosfluorescently",
+ "proactively",
+ "professionally",
+ "progressively",
+ "quickly",
+ "rapidiously",
+ "seamlessly",
+ "synergistically",
+ "uniquely",
+}
+
+// verb word list
+var verbs = []string {
+ "actualize",
+ "administrate",
+ "aggregate",
+ "architect",
+ "benchmark",
+ "brand",
+ "build",
+ "cloudify",
+ "communicate",
+ "conceptualize",
+ "coordinate",
+ "create",
+ "cultivate",
+ "customize",
+ "deliver",
+ "deploy",
+ "develop",
+ "dinintermediate",
+ "disseminate",
+ "drive",
+ "embrace",
+ "e-enable",
+ "empower",
+ "enable",
+ "engage",
+ "engineer",
+ "enhance",
+ "envisioneer",
+ "evisculate",
+ "evolve",
+ "expedite",
+ "exploit",
+ "extend",
+ "fabricate",
+ "facilitate",
+ "fashion",
+ "formulate",
+ "foster",
+ "generate",
+ "grow",
+ "harness",
+ "ideate",
+ "impact",
+ "implement",
+ "incentivize",
+ "incubate",
+ "instantiate",
+ "initiate",
+ "innovate",
+ "integrate",
+ "iterate",
+ "leverage existing",
+ "leverage other's",
+ "maintain",
+ "matrix",
+ "maximize",
+ "mesh",
+ "monetize",
+ "morph",
+ "myocardinate",
+ "negotiate",
+ "network",
+ "optimize",
+ "orchestrate",
+ "parallel task",
+ "plagiarize",
+ "pontificate",
+ "predominate",
+ "productivate",
+ "productize",
+ "promote",
+ "provide access to",
+ "pursue",
+ "recaptiualize",
+ "reconceptualize",
+ "redefine",
+ "re-engineer",
+ "reintermediate",
+ "reinvent",
+ "repurpose",
+ "restore",
+ "revolutionize",
+ "scale",
+ "seize",
+ "simplify",
+ "strategize",
+ "streamline",
+ "supply",
+ "syndicate",
+ "synergize",
+ "synthesize",
+ "target",
+ "transform",
+ "transition",
+ "underwhelm",
+ "unleash",
+ "utilize",
+ "visualize",
+ "whiteboard",
+}
+
+// adjective word list
+var adjectives = []string {
+ "24/7",
+ "24/365",
+ "accurate",
+ "adaptive",
+ "alternative",
+ "an expanded array of",
+ "B2B",
+ "B2C",
+ "backend",
+ "backward-compatible",
+ "best-of-breed",
+ "bleeding-edge",
+ "bricks-and-clicks",
+ "business",
+ "clicks-and-mortar",
+ "client-based",
+ "client-centered",
+ "client-centric",
+ "client-focused",
+ "collaborative",
+ "compelling",
+ "competitive",
+ "cooperative",
+ "corporate",
+ "cost effective",
+ "covalent",
+ "cross functional",
+ "cross-media",
+ "cross-platform",
+ "cross-unit",
+ "customer directed",
+ "customized",
+ "cutting-edge",
+ "distinctive",
+ "distributed",
+ "diverse",
+ "dynamic",
+ "e-business",
+ "economically sound",
+ "effective",
+ "efficient",
+ "emerging",
+ "empowered",
+ "enabled",
+ "end-to-end",
+ "enterprise",
+ "enterprise-wide",
+ "equity invested",
+ "error-free",
+ "ethical",
+ "excellent",
+ "exceptional",
+ "extensible",
+ "extensive",
+ "flexible",
+ "focused",
+ "frictionless",
+ "front-end",
+ "fully researched",
+ "fully tested",
+ "functional",
+ "functionalized",
+ "fungible",
+ "future-proof",
+ "global",
+ "go forward",
+ "goal-oriented",
+ "granular",
+ "high standards in",
+ "high-payoff",
+ "high-quality",
+ "highly efficient",
+ "holistic",
+ "impactful",
+ "inexpensive",
+ "innovative",
+ "installed base",
+ "integrated",
+ "interactive",
+ "interdependent",
+ "intermandated",
+ "interoperable",
+ "intuitive",
+ "just in time",
+ "leading-edge",
+ "leveraged",
+ "long-term high-impact",
+ "low-risk high-yield",
+ "magnetic",
+ "maintainable",
+ "market positioning",
+ "market-driven",
+ "mission-critical",
+ "multidisciplinary",
+ "multifunctional",
+ "multimedia based",
+ "next-generation",
+ "one-to-one",
+ "open-source",
+ "optimal",
+ "orthogonal",
+ "out-of-the-box",
+ "pandemic",
+ "parallel",
+ "performance based",
+ "plug-and-play",
+ "premier",
+ "premium",
+ "principle-centered",
+ "proactive",
+ "process-centric",
+ "professional",
+ "progressive",
+ "prospective",
+ "quality",
+ "real-time",
+ "reliable",
+ "resource sucking",
+ "resource maximizing",
+ "resource-leveling",
+ "revolutionary",
+ "robust",
+ "scalable",
+ "seamless",
+ "stand-alone",
+ "standardized",
+ "standards compliant",
+ "state of the art",
+ "sticky",
+ "strategic",
+ "superior",
+ "sustainable",
+ "synergistic",
+ "tactical",
+ "team building",
+ "team driven",
+ "technically sound",
+ "timely",
+ "top-line",
+ "transparent",
+ "turnkey",
+ "ubiquitous",
+ "unique",
+ "user-centric",
+ "user friendly",
+ "value-added",
+ "vertical",
+ "viral",
+ "virtual",
+ "visionary",
+ "web-enabled",
+ "wireless",
+ "world-class",
+ "worldwide",
+}
+
+// noun word list
+// (note: added "A.I." for 2025)
+var nouns = []string {
+ "action items",
+ "alignments",
+ "applications",
+ "architectures",
+ "bandwidth",
+ "benefits",
+ "best practices",
+ "catalysts for change",
+ "channels",
+ "clouds",
+ "collaboration and idea-sharing",
+ "communities",
+ "content",
+ "convergence",
+ "core competencies",
+ "customer service",
+ "data",
+ "deliverables",
+ "e-business",
+ "e-commerce",
+ "e-markets",
+ "e-tailers",
+ "e-services",
+ "experiences",
+ "expertise",
+ "functionalities",
+ "fungibility",
+ "growth strategies",
+ "human capital",
+ "ideas",
+ "imperatives",
+ "infomediaries",
+ "information",
+ "infrastructures",
+ "initiatives",
+ "innovation",
+ "intellectual capital",
+ "interfaces",
+ "internal or \"organic\" sources",
+ "leadership",
+ "leadership skills",
+ "manufactured products",
+ "markets",
+ "materials",
+ "meta-services",
+ "methodologies",
+ "methods of empowerment",
+ "metrics",
+ "mindshare",
+ "models",
+ "networks",
+ "niches",
+ "niche markets",
+ "nosql",
+ "opportunities",
+ "\"outside the box\" thinking",
+ "outsourcing",
+ "paradigms",
+ "partnerships",
+ "platforms",
+ "portals",
+ "potentialities",
+ "process improvements",
+ "processes",
+ "products",
+ "quality vectors",
+ "relationships",
+ "resources",
+ "results",
+ "ROI",
+ "scenarios",
+ "schemas",
+ "services",
+ "solutions",
+ "sources",
+ "strategic theme areas",
+ "storage",
+ "supply chains",
+ "synergy",
+ "systems",
+ "technologies",
+ "technology",
+ "testing procedures",
+ "total linkage",
+ "users",
+ "value",
+ "vortals",
+ "web-readiness",
+ "web services",
+ "A.I.",
+ "blockchain",
+}
+
+var joins = []string {
+ "for",
+ "and",
+ "while",
+ "by",
+ "to",
+}
+
+var delims = []string {
+ " ",
+ ", ",
+ " and ",
+ ", and ",
+}
+
+type Phrase struct {
+ HasAdverb bool // does this phrase have an adverb?
+ Adverb int // word position in adverbs
+ Verb int // word position in verbs
+ Adjectives []int // word positions in adjectives
+ Noun int // word position in nouns
+}
+
+// convert phrase to string
+func (p Phrase) String() string {
+ var b strings.Builder
+
+ // append adverb
+ if p.HasAdverb {
+ b.WriteString(adverbs[p.Adverb])
+ b.WriteString(" ")
+ }
+
+ // append verb
+ b.WriteString(verbs[p.Verb])
+ b.WriteString(" ")
+
+ // append adjectives
+ for i, adjective := range(p.Adjectives) {
+ b.WriteString(adjectives[adjective]) // append adjective
+
+ if len(p.Adjectives) == 1 {
+ b.WriteString(" ") // append tail " "
+ } else if len(p.Adjectives) == 2 {
+ if i == 0 {
+ b.WriteString(" and ") // append delimiting " and "
+ } else {
+ b.WriteString(" ") // append tail " "
+ }
+ } else if len(p.Adjectives) > 2 {
+ if i == len(p.Adjectives) - 2 {
+ b.WriteString(", and ") // append ", and "
+ } else if i < len(p.Adjectives) - 2 {
+ b.WriteString(", ") // append delimiting ", "
+ } else {
+ b.WriteString(" ") // append tail " "
+ }
+ }
+ }
+
+ // append noun
+ b.WriteString(nouns[p.Noun])
+
+ // return string
+ return b.String()
+}
+
+type Sentence struct {
+ Phrases []Phrase // phrases
+ Joins []int // phrase joins
+}
+
+// get 0-5 unique adjectives
+func uniqueAdjectives() []int {
+ num := rand.IntN(5) // number of adjectives
+
+ lut := make(map[int]bool)
+ for len(lut) < num {
+ n := rand.IntN(len(adjectives))
+ if !lut[n] {
+ lut[n] = true
+ }
+ }
+
+ // build result
+ r := make([]int, 0, num)
+ for n := range(lut) {
+ r = append(r, n)
+ }
+
+ // return result
+ return r
+}
+
+// Create sentence
+func NewSentence() Sentence {
+ phrases := make([]Phrase, 1 + rand.IntN(3))
+ for i := range(len(phrases)) {
+ // adverb
+ phrases[i].HasAdverb = (rand.IntN(2) == 1)
+ phrases[i].Adverb = rand.IntN(len(adverbs))
+
+ phrases[i].Verb = rand.IntN(len(verbs)) // verb
+ phrases[i].Adjectives = uniqueAdjectives() // adjectives
+ phrases[i].Noun = rand.IntN(len(nouns)) // noun
+ }
+
+ // return sentence
+ return Sentence {
+ Phrases: phrases,
+ Joins: rand.Perm(len(joins))[:len(phrases)],
+ }
+}
+
+// Convert sentence to string.
+func (s Sentence) String() string {
+ var b strings.Builder
+ for i, phrase := range(s.Phrases) {
+ b.WriteString(phrase.String()) // append phrase
+ if len(s.Phrases) > 1 && i < len(s.Phrases) - 1 {
+ // append space, join, space
+ b.WriteString(" ")
+ b.WriteString(joins[s.Joins[i]])
+ b.WriteString(" ")
+ }
+ }
+
+ // return string
+ return b.String()
+}
+
+// Return one randomly generated sentence.
+func String() string {
+ return NewSentence().String()
+}
+
+// Return a slice of `num` randomly generated sentences.
+func N(num int) []string {
+ r := make([]string, num)
+ for i := range(num) {
+ r[i] = String()
+ }
+
+ return r
+}
diff --git a/bot/bot_test.go b/bot/bot_test.go
new file mode 100644
index 0000000..122614f
--- /dev/null
+++ b/bot/bot_test.go
@@ -0,0 +1,104 @@
+package bot
+
+import "testing"
+
+func TestPhraseString(t *testing.T) {
+ passTests := []struct {
+ val Phrase // test value
+ exp string // expected string
+ } {{
+ val: Phrase {
+ Verb: 0,
+ Noun: 0,
+ },
+ exp: "actualize action items",
+ }, {
+ val: Phrase {
+ HasAdverb: true,
+ Adverb: 1,
+ Verb: 2,
+ Noun: 3,
+ },
+ exp: "assertively aggregate architectures",
+ }, {
+ val: Phrase {
+ HasAdverb: true,
+ Adverb: 2,
+ Verb: 3,
+ Adjectives: []int { 4 },
+ Noun: 5,
+ },
+ exp: "authoritatively architect alternative benefits",
+ }, {
+ val: Phrase {
+ HasAdverb: true,
+ Adverb: 3,
+ Verb: 4,
+ Adjectives: []int { 5, 6 },
+ Noun: 7,
+ },
+ exp: "collaboratively benchmark an expanded array of and B2B catalysts for change",
+ }, {
+ val: Phrase {
+ HasAdverb: true,
+ Adverb: 4,
+ Verb: 5,
+ Adjectives: []int { 6, 7, 8 },
+ Noun: 9,
+ },
+ exp: "compellingly brand B2B, B2C, and backend clouds",
+ }}
+
+ for _, test := range(passTests) {
+ t.Run(test.exp, func(t *testing.T) {
+ got := test.val.String()
+ if got != test.exp {
+ t.Fatalf("got \"%s\", exp \"%s\"", got, test.exp)
+ }
+ })
+ }
+}
+
+func TestSentenceString(t *testing.T) {
+ passTests := []struct {
+ val Sentence // test value
+ exp string // expected string
+ } {{
+ val: Sentence{
+ Phrases: []Phrase{
+ Phrase {
+ Verb: 0,
+ Noun: 0,
+ },
+ },
+ },
+ exp: "actualize action items",
+ }, {
+ val: Sentence {
+ Phrases: []Phrase {
+ Phrase {
+ Verb: 0,
+ Noun: 0,
+ },
+
+ Phrase {
+ HasAdverb: true,
+ Adverb: 1,
+ Verb: 2,
+ Noun: 3,
+ },
+ },
+ Joins: []int { 0 },
+ },
+ exp: "actualize action items for assertively aggregate architectures",
+ }}
+
+ for _, test := range(passTests) {
+ t.Run(test.exp, func(t *testing.T) {
+ got := test.val.String()
+ if got != test.exp {
+ t.Fatalf("got \"%s\", exp \"%s\"", got, test.exp)
+ }
+ })
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..a3bd7ad
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module pablotron.org/jim-bot
+
+go 1.23.0
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..d6d7455
--- /dev/null
+++ b/main.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "strings"
+
+ "pablotron.org/jim-bot/bot"
+)
+
+// serve home page
+func doHome(w http.ResponseWriter, _ *http.Request) {
+ // generate respones body
+ body := []byte(strings.Join(bot.N(10), "\n") + "\n")
+
+ // write header and response
+ w.Header().Add("Content-Type", "text/plain")
+ if _, err := w.Write(body); err != nil {
+ log.Print(err)
+ }
+}
+
+func main() {
+ http.HandleFunc("/", doHome)
+ http.ListenAndServe(":8080", nil)
+}