Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions assets/flags/README.txt

This file was deleted.

Binary file removed assets/flags/small/ak.png
Binary file not shown.
Binary file removed assets/flags/small/al.png
Binary file not shown.
Binary file removed assets/flags/small/ar.png
Binary file not shown.
Binary file removed assets/flags/small/az.png
Binary file not shown.
Binary file removed assets/flags/small/ca.png
Binary file not shown.
Binary file removed assets/flags/small/co.png
Binary file not shown.
Binary file removed assets/flags/small/ct.png
Binary file not shown.
Binary file removed assets/flags/small/de.png
Binary file not shown.
Binary file removed assets/flags/small/fl.png
Binary file not shown.
Binary file removed assets/flags/small/ga.png
Binary file not shown.
Binary file removed assets/flags/small/hi.png
Binary file not shown.
Binary file removed assets/flags/small/ia.png
Binary file not shown.
Binary file removed assets/flags/small/id.png
Binary file not shown.
Binary file removed assets/flags/small/il.png
Binary file not shown.
Binary file removed assets/flags/small/in.png
Binary file not shown.
Binary file removed assets/flags/small/ks.png
Binary file not shown.
Binary file removed assets/flags/small/ky.png
Binary file not shown.
Binary file removed assets/flags/small/la.png
Binary file not shown.
Binary file removed assets/flags/small/ma.png
Binary file not shown.
Binary file removed assets/flags/small/md.png
Binary file not shown.
Binary file removed assets/flags/small/me.png
Binary file not shown.
Binary file removed assets/flags/small/mi.png
Binary file not shown.
Binary file removed assets/flags/small/mn.png
Binary file not shown.
Binary file removed assets/flags/small/mo.png
Binary file not shown.
Binary file removed assets/flags/small/ms.png
Binary file not shown.
Binary file removed assets/flags/small/mt.png
Diff not rendered.
Binary file removed assets/flags/small/nc.png
Diff not rendered.
Binary file removed assets/flags/small/nd.png
Diff not rendered.
Binary file removed assets/flags/small/ne.png
Diff not rendered.
Binary file removed assets/flags/small/nh.png
Diff not rendered.
Binary file removed assets/flags/small/nj.png
Diff not rendered.
Binary file removed assets/flags/small/nm.png
Diff not rendered.
Binary file removed assets/flags/small/nv.png
Diff not rendered.
Binary file removed assets/flags/small/ny.png
Diff not rendered.
Binary file removed assets/flags/small/oh.png
Diff not rendered.
Binary file removed assets/flags/small/ok.png
Diff not rendered.
Binary file removed assets/flags/small/or.png
Diff not rendered.
Binary file removed assets/flags/small/pa.png
Diff not rendered.
Binary file removed assets/flags/small/ri.png
Diff not rendered.
Binary file removed assets/flags/small/sc.png
Diff not rendered.
Binary file removed assets/flags/small/sd.png
Diff not rendered.
Binary file removed assets/flags/small/tn.png
Diff not rendered.
Binary file removed assets/flags/small/tx.png
Diff not rendered.
Binary file removed assets/flags/small/ut.png
Diff not rendered.
Binary file removed assets/flags/small/va.png
Diff not rendered.
Binary file removed assets/flags/small/vt.png
Diff not rendered.
Binary file removed assets/flags/small/wa.png
Diff not rendered.
Binary file removed assets/flags/small/wi.png
Diff not rendered.
Binary file removed assets/flags/small/wv.png
Diff not rendered.
Binary file removed assets/flags/small/wy.png
Diff not rendered.
6 changes: 3 additions & 3 deletions pkg/chatbot/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@

func (a *App) flagCmd(ctx context.Context, user *users.User, _ []string) {
slog.InfoContext(ctx, "ran !flag", "username", user.Username)
a.Onscreens.ShowFlag(ctx, 10*time.Second)
a.Onscreens.ShowFlag(ctx, a.Video.Current().State, 10*time.Second)

Check failure on line 81 in pkg/chatbot/commands.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/chatbot/commands.go#L81

Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
Raw output
pkg/chatbot/commands.go:81:22: Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
	a.Onscreens.ShowFlag(ctx, a.Video.Current().State, 10*time.Second)
	                    ^
}

func (a *App) versionCmd(ctx context.Context, user *users.User, _ []string) {
Expand Down Expand Up @@ -407,7 +407,7 @@
if strings.ToLower(guess) == strings.ToLower(vid.State) {
msg = fmt.Sprintf("@%s got it! We're in %s", user.Username, vid.State)
// show the flag for the state
a.Onscreens.ShowFlag(ctx, 10*time.Second)
a.Onscreens.ShowFlag(ctx, vid.State, 10*time.Second)

Check failure on line 410 in pkg/chatbot/commands.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/chatbot/commands.go#L410

Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
Raw output
pkg/chatbot/commands.go:410:23: Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
		a.Onscreens.ShowFlag(ctx, vid.State, 10*time.Second)
		                    ^
// increase their guess score
user.AddToScore(ctx, guessScoreboard, 1.0)
user.AddToScore(ctx, scoreboards.CurrentGuessScoreboard(), 1.0)
Expand All @@ -428,7 +428,7 @@
}
msg := fmt.Sprintf("We're in %s", vid.State)
// show the flag for the state
a.Onscreens.ShowFlag(ctx, 10*time.Second)
a.Onscreens.ShowFlag(ctx, vid.State, 10*time.Second)

Check failure on line 431 in pkg/chatbot/commands.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/chatbot/commands.go#L431

Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
Raw output
pkg/chatbot/commands.go:431:22: Error return value of `a.Onscreens.ShowFlag` is not checked (errcheck)
	a.Onscreens.ShowFlag(ctx, vid.State, 10*time.Second)
	                    ^
// record that they know the location now
user.SetLastLocationTime()
a.IRC.Say(msg)
Expand Down
8 changes: 4 additions & 4 deletions pkg/chatbot/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ func TestStateCmd_DrivesShowFlagOverlay(t *testing.T) {
// --- flagCmd ---

func TestFlagCmd_DrivesShowFlagOverlay(t *testing.T) {
app := newTestApp(video.Video{})
app := newTestApp(video.Video{State: "Missouri"})
rec := &recordingOnscreens{}
app.Onscreens = rec

Expand All @@ -585,8 +585,8 @@ func TestFlagCmd_DrivesShowFlagOverlay(t *testing.T) {

app.flagCmd(context.Background(), newTestUser("viewer1"), nil)

if len(rec.Calls) != 1 || rec.Calls[0] != "ShowFlag(10s)" {
t.Errorf("expected ShowFlag(10s) overlay call, got %v", rec.Calls)
if len(rec.Calls) != 1 || rec.Calls[0] != "ShowFlag(Missouri,10s)" {
t.Errorf("expected ShowFlag(Missouri,10s) overlay call, got %v", rec.Calls)
}
}

Expand Down Expand Up @@ -751,7 +751,7 @@ func TestGuessCmd_CorrectGuess_DrivesOverlayAndPlayback(t *testing.T) {
}

// Overlay sequence: ShowFlag (state flag) then ShowTimewarp (from a.timewarp()).
wantOverlay := []string{"ShowFlag(10s)", "ShowTimewarp()"}
wantOverlay := []string{"ShowFlag(Colorado,10s)", "ShowTimewarp()"}
if len(recOverlay.Calls) != len(wantOverlay) {
t.Fatalf("expected %d overlay calls, got %d: %v", len(wantOverlay), len(recOverlay.Calls), recOverlay.Calls)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/chatbot/onscreens.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
// commands depend on. Tests inject a fake; production uses the
// realOnscreens adapter wired in defaultApp.
type Onscreens interface {
ShowFlag(ctx context.Context, dur time.Duration) error
ShowFlag(ctx context.Context, state string, dur time.Duration) error
ShowLeaderboard(ctx context.Context, title string, leaderboard [][]string) error
HideMiddleText(ctx context.Context) error
ShowMiddleText(ctx context.Context, msg string) error
Expand All @@ -28,8 +28,8 @@ type realOnscreens struct {
c *onscreensClient.Client
}

func (r realOnscreens) ShowFlag(ctx context.Context, dur time.Duration) error {
return r.c.ShowFlag(ctx, dur)
func (r realOnscreens) ShowFlag(ctx context.Context, state string, dur time.Duration) error {
return r.c.ShowFlag(ctx, state, dur)
}
func (r realOnscreens) ShowLeaderboard(ctx context.Context, title string, lb [][]string) error {
return r.c.ShowLeaderboard(ctx, title, lb)
Expand Down
6 changes: 3 additions & 3 deletions pkg/chatbot/onscreens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
// overlay surface — it just swallows every call.
type noopOnscreens struct{}

func (noopOnscreens) ShowFlag(_ context.Context, _ time.Duration) error { return nil }
func (noopOnscreens) ShowFlag(_ context.Context, _ string, _ time.Duration) error { return nil }
func (noopOnscreens) ShowLeaderboard(_ context.Context, _ string, _ [][]string) error { return nil }
func (noopOnscreens) HideMiddleText(_ context.Context) error { return nil }
func (noopOnscreens) ShowMiddleText(_ context.Context, _ string) error { return nil }
Expand All @@ -23,8 +23,8 @@ type recordingOnscreens struct {
Calls []string
}

func (r *recordingOnscreens) ShowFlag(_ context.Context, dur time.Duration) error {
r.Calls = append(r.Calls, fmt.Sprintf("ShowFlag(%s)", dur))
func (r *recordingOnscreens) ShowFlag(_ context.Context, state string, dur time.Duration) error {
r.Calls = append(r.Calls, fmt.Sprintf("ShowFlag(%s,%s)", state, dur))
return nil
}
func (r *recordingOnscreens) ShowLeaderboard(_ context.Context, title string, lb [][]string) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/chatbot/playback.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (a *App) jumpCmd(ctx context.Context, user *users.User, params []string) {
// update the currently-playing video
a.Video.GetCurrentlyPlaying(ctx)
// show the flag for the state
a.Onscreens.ShowFlag(ctx, 10*time.Second)
a.Onscreens.ShowFlag(ctx, randomVid.State, 10*time.Second)
// update our record of last time it ran
lastTimewarpTime = time.Now()
}
Expand Down
34 changes: 29 additions & 5 deletions pkg/onscreens-client/nats_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,16 +164,40 @@ func TestEmptyPayloadCommandsPublish(t *testing.T) {
}
}

// TestShowFlagDoesNotPublish asserts the disabled flag.show stays a no-op
// (no subject in the taxonomy, so nothing is published).
func TestShowFlagDoesNotPublish(t *testing.T) {
// TestShowFlagPublishes asserts flag.show publishes a FlagShow carrying the
// state normalized to its two-letter abbrev.
func TestShowFlagPublishes(t *testing.T) {
rec := &recordingPublisher{}
c := New(okServer(t), rec, "stage")
if err := c.ShowFlag(context.Background(), 10); err != nil {
if err := c.ShowFlag(context.Background(), "Missouri", 10); err != nil {
t.Fatalf("ShowFlag: %v", err)
}
if len(rec.Publishes) != 1 {
t.Fatalf("expected 1 publish, got %d", len(rec.Publishes))
}
pub := rec.Publishes[0]
if pub.Subject != "tripbot.stage.onscreens.flag.show" {
t.Errorf("subject = %q, want tripbot.stage.onscreens.flag.show", pub.Subject)
}
var ev oe.FlagShow
if err := json.Unmarshal(pub.Payload, &ev); err != nil {
t.Fatalf("payload not valid JSON: %v", err)
}
if ev.State != "MO" {
t.Errorf("state = %q, want MO", ev.State)
}
}

// TestShowFlagUnknownStateNoPublish asserts a state with no known abbrev is a
// no-op (nothing to show).
func TestShowFlagUnknownStateNoPublish(t *testing.T) {
rec := &recordingPublisher{}
c := New(okServer(t), rec, "stage")
if err := c.ShowFlag(context.Background(), "Atlantis", 10); err != nil {
t.Fatalf("ShowFlag: %v", err)
}
if len(rec.Publishes) != 0 {
t.Errorf("expected 0 publishes for disabled flag.show, got %d", len(rec.Publishes))
t.Errorf("expected 0 publishes for unknown state, got %d", len(rec.Publishes))
}
}

Expand Down
28 changes: 19 additions & 9 deletions pkg/onscreens-client/onscreens-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,25 @@ func (c *Client) ShowTimewarp(ctx context.Context) error {
return nil
}

func (c *Client) ShowFlag(ctx context.Context, dur time.Duration) error {
//TODO: bring this back
// url := c.serverURL + "/onscreens/flag/show"
// url = fmt.Sprintf("%s?duration=%s", url, helpers.Base64Encode(string(rune(dur))))
// _, err := c.get(ctx, url)
// if err != nil {
// slog.ErrorContext(ctx, "error showing flag onscreen", "err", err)
// return err
// }
// ShowFlag displays the flag for the given state. state may be a full name
// ("Missouri") or a two-letter abbrev ("MO"); it's normalized to the abbrev
// the server keys its embedded flag images on. The overlay's display duration
// is owned by the server (flagDuration), so dur is not transported — kept in
// the signature for symmetry with the other timed overlays. A state with no
// known flag is a no-op (nothing to show).
func (c *Client) ShowFlag(ctx context.Context, state string, dur time.Duration) error {
abbrev := state
if len(abbrev) != 2 {
abbrev = helpers.StateToStateAbbrev(state)
}
if abbrev == "" {
slog.WarnContext(ctx, "ShowFlag: no flag for state", "state", state)
return nil
}
c.publish(ctx, oe.FlagShowSubject(c.env), oe.FlagShow{
Envelope: oe.NewEnvelope(),
State: strings.ToUpper(abbrev),
})
return nil
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/onscreens-events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ type MiddleShow struct {
Msg string `json:"msg"`
}

// FlagShow is the payload for the flag.show subject. State is a two-letter
// US state abbreviation (e.g. "MO"); the server maps it to an embedded flag
// image and falls back to a transparent placeholder if it has no flag for
// that state.
type FlagShow struct {
Envelope
State string `json:"state"`
}

// LeaderboardShow is the payload for the leaderboard.show subject. The
// server renders Rows into the on-screen HTML, so the wire carries
// structured data rather than a pre-rendered blob.
Expand Down
6 changes: 3 additions & 3 deletions pkg/onscreens-events/subjects.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TimewarpHideSubject(env string) string { return subject(env, "timewarp",
func GPSShowSubject(env string) string { return subject(env, "gps", "show") }
func GPSHideSubject(env string) string { return subject(env, "gps", "hide") }

// FlagHideSubject is the only flag subject. flag.show is intentionally
// absent — the feature is disabled (the HTTP route 501s and the client
// method is a no-op), so publishing it would be dead surface.
// FlagShow carries the state whose flag to display (FlagShow.State); the
// server resolves it to an embedded per-state flag image.
func FlagShowSubject(env string) string { return subject(env, "flag", "show") }
func FlagHideSubject(env string) string { return subject(env, "flag", "hide") }
5 changes: 5 additions & 0 deletions pkg/onscreens-server/assets/flags/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
US state flag PNGs (lowercase state abbreviation), downloaded from
https://usa.flagpedia.net/download. Embedded by pkg/onscreens-server
(see browser.go's flagsFS) and served per-state by the flag onscreen.
Relocated here from the repo-root assets/flags/ so Go's //go:embed can
reach them (embed can't cross above the package directory).
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
42 changes: 36 additions & 6 deletions pkg/onscreens-server/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ import (
"html/template"
"log/slog"
"net/http"
"strings"

"github.com/gorilla/mux"
)

// flagPlaceholderPNG is a 1×1 transparent PNG served by the flag asset
// endpoint while the state-driven flag swap is disabled (see flag.go's
// TODO). The browser source's <img> tag fetches this URL even when the
// onscreen is hidden, so we serve a valid PNG to keep the request quiet
// rather than 404.
// endpoint when no flag should be shown — the browser source's <img> tag
// fetches this URL even when the onscreen is hidden, and it's also the
// fallback for a state we have no flag image for. Serving a valid PNG keeps
// the request quiet rather than 404.
var flagPlaceholderPNG = []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
Expand All @@ -40,6 +41,27 @@ var onscreenTmpl = template.Must(template.ParseFS(onscreenTemplates, "templates/
//go:embed assets/GPS.png
var gpsPNG []byte

// flagsFS holds the per-state flag images (assets/flags/<abbrev>.png,
// lowercase two-letter abbreviation), embedded so the slim runtime image is
// self-contained. The flag asset handler serves the one matching the flag
// onscreen's current state, falling back to flagPlaceholderPNG.
//
//go:embed assets/flags
var flagsFS embed.FS

// flagPNG returns the embedded flag image for a US state abbreviation (any
// case), or nil if there's no flag for it.
func flagPNG(abbrev string) []byte {
if abbrev == "" {
return nil
}
b, err := flagsFS.ReadFile("assets/flags/" + strings.ToLower(abbrev) + ".png")
if err != nil {
return nil
}
return b
}

// onscreenStyle controls how a single onscreen renders in its OBS browser source.
// Keep these in sync with the dimensions / fonts that the previous text_ft2_source
// and image_source entries in infra/docker/obs/config/Tripbot.json.tmpl used.
Expand Down Expand Up @@ -134,10 +156,18 @@ func (s *Server) onscreensAssetHandler(w http.ResponseWriter, r *http.Request) {
slog.ErrorContext(r.Context(), "writing gps image", "err", err)
}
case SlugFlag:
// Serve the flag for the onscreen's current state (stored in
// Content as a state abbrev by handleFlagShow); fall back to the
// transparent placeholder when no state is set or we have no flag
// for it.
img := flagPNG(s.Flag.Content)
if img == nil {
img = flagPlaceholderPNG
}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Cache-Control", "no-store")
if _, err := w.Write(flagPlaceholderPNG); err != nil {
slog.ErrorContext(r.Context(), "writing flag placeholder", "err", err)
if _, err := w.Write(img); err != nil {
slog.ErrorContext(r.Context(), "writing flag image", "err", err)
}
default:
http.NotFound(w, r)
Expand Down
18 changes: 12 additions & 6 deletions pkg/onscreens-server/flag.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package onscreensServer

import "log/slog"
import (
"log/slog"
"time"
)

// flagDuration is how long the state flag overlay stays up after a flag.show.
// Server-owned (like gpsDuration) so the publisher doesn't transport it.
var flagDuration = 10 * time.Second

// newFlagOnscreen constructs the flag *Onscreen.
//
// The state-driven flag swap is currently disabled — see
// onscreens-client.ShowFlag (no-op) and the placeholder served by the
// /onscreens/asset/flag handler. Bringing it back means re-implementing
// the per-state image picker (was flagSourceFile + updateFlagFile,
// removed in the disk-write cleanup).
// The flag onscreen's Content holds the current state abbreviation (set by
// handleFlagShow); the /onscreens/asset/flag handler resolves it to the
// matching embedded per-state flag image, falling back to a transparent
// placeholder for an unset or unknown state.
func newFlagOnscreen() *Onscreen {
slog.Info("creating onscreen", "kind", "flag")
return newOnscreen()
Expand Down
34 changes: 11 additions & 23 deletions pkg/onscreens-server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,28 @@ import (
"fmt"
"log/slog"
"net/http"
"strings"

"github.com/adanalife/tripbot/pkg/helpers"
oe "github.com/adanalife/tripbot/pkg/onscreens-events"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
)

// onscreensFlagHandler is the HTTP twin of the flag.show / flag.hide NATS
// path (the client publishes over NATS; this stays for parity + manual
// debugging, e.g. curl '/onscreens/flag/show?state=MO'). show takes a
// two-letter state abbrev, which the asset handler resolves to the embedded
// per-state flag image.
func (s *Server) onscreensFlagHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
spew.Dump(vars)

switch vars["action"] {
switch mux.Vars(r)["action"] {
case "show":
base64content, ok := r.URL.Query()["duration"]
if !ok || len(base64content) > 1 {
state := r.URL.Query().Get("state")
if state == "" {
http.Error(w, "417 expectation failed", http.StatusExpectationFailed)
return
}
//TODO: fix this
http.Error(w, "501 not implemented", http.StatusNotImplemented)
return
//durStr, err := helpers.Base64Decode(base64content[0])
//if err != nil {
// slog.ErrorContext(r.Context(), "unable to decode string", "err", err)
// http.Error(w, "422 unable to decode string", http.StatusUnprocessableEntity)
// return
//}
//dur, err := time.ParseDuration(durStr)
//if err != nil {
// http.Error(w, "422 unable to parse duration", http.StatusUnprocessableEntity)
// return
//}
//s.Flag.ShowFor("", dur)
//fmt.Fprintf(w, "OK")
s.Flag.ShowFor(strings.ToUpper(state), flagDuration)
fmt.Fprintf(w, "OK")
case "hide":
s.Flag.Hide()
fmt.Fprintf(w, "OK")
Expand Down
Loading
Loading