diff options
| author | Xe Iaso <me@xeiaso.net> | 2023-02-04 18:36:39 -0500 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2023-02-04 18:36:39 -0500 |
| commit | 2ef631b425070fd3d9a0581ee9f77f563edebe20 (patch) | |
| tree | 2f1887937da8eb5b2b5a16fb3ea122c6b0da3c0a | |
| parent | 1d2bcebb5289372015967aaff7e6cf7029e0c7c6 (diff) | |
| download | x-2ef631b425070fd3d9a0581ee9f77f563edebe20.tar.xz x-2ef631b425070fd3d9a0581ee9f77f563edebe20.zip | |
mastodon/robocadey2: contact logos over tsnet
Signed-off-by: Xe Iaso <me@xeiaso.net>
| -rw-r--r-- | flake.nix | 22 | ||||
| -rw-r--r-- | internal/stablediffusion/stablediffusion.go | 16 | ||||
| -rw-r--r-- | mastodon/robocadey2/build.go | 25 | ||||
| -rw-r--r-- | mastodon/robocadey2/fly.toml | 26 | ||||
| -rw-r--r-- | mastodon/robocadey2/main.go | 112 | ||||
| -rw-r--r-- | web/mastodon/status.go | 3 |
6 files changed, 179 insertions, 25 deletions
@@ -58,7 +58,7 @@ version = "1.2.3"; src = ./.; modules = ./gomod2nix.toml; - subPackages = [ "./cmd/xedn" ]; + subPackages = [ "cmd/xedn" ]; buildInputs = with pkgs; [ pkg-config @@ -67,6 +67,14 @@ ]; }; + robocadey2 = pkgs.buildGoApplication { + pname = "robocadey2"; + version = "1.2.3"; + src = ./.; + modules = ./gomod2nix.toml; + subPackages = [ "mastodon/robocadey2" ]; + }; + copyFile = { pname, path ? pname }: pkgs.stdenv.mkDerivation { inherit pname; @@ -97,7 +105,7 @@ path = "make-mastodon-app"; }; - inherit xedn; + inherit xedn robocadey2; aegis = copyFile { pname = "aegis"; }; cadeybot = copyFile { pname = "cadeybot"; }; @@ -111,6 +119,16 @@ whoisfront = copyFile { pname = "whoisfront"; }; within-website = copyFile { pname = "within.website"; }; + robocadey2-docker = pkgs.dockerTools.buildLayeredImage { + name = "registry.fly.io/xe-robocadey2"; + tag = "latest"; + contents = [ pkgs.cacert ]; + config = { + Cmd = [ "${robocadey2}/bin/robocadey2" ]; + WorkingDir = "${robocadey2}"; + }; + }; + xedn-docker = pkgs.dockerTools.buildLayeredImage { name = "registry.fly.io/xedn"; tag = "latest"; diff --git a/internal/stablediffusion/stablediffusion.go b/internal/stablediffusion/stablediffusion.go index 218bf51..3ef2bae 100644 --- a/internal/stablediffusion/stablediffusion.go +++ b/internal/stablediffusion/stablediffusion.go @@ -83,7 +83,21 @@ type ImageInfo struct { IsUsingInpaintingConditioning bool `json:"is_using_inpainting_conditioning"` } +var ( + Default *Client = &Client{ + HTTP: http.DefaultClient, + } +) + func Generate(ctx context.Context, inp SimpleImageRequest) (*ImageResponse, error) { + return Default.Generate(ctx, inp) +} + +type Client struct { + HTTP *http.Client +} + +func (c *Client) Generate(ctx context.Context, inp SimpleImageRequest) (*ImageResponse, error) { u, err := buildURL("/sdapi/v1/txt2img") if err != nil { return nil, fmt.Errorf("error building URL: %w", err) @@ -99,7 +113,7 @@ func Generate(ctx context.Context, inp SimpleImageRequest) (*ImageResponse, erro return nil, fmt.Errorf("error making request: %w", err) } - resp, err := http.DefaultClient.Do(req) + resp, err := c.HTTP.Do(req) if err != nil { return nil, fmt.Errorf("error fetching response: %w", err) } diff --git a/mastodon/robocadey2/build.go b/mastodon/robocadey2/build.go new file mode 100644 index 0000000..3310bd4 --- /dev/null +++ b/mastodon/robocadey2/build.go @@ -0,0 +1,25 @@ +//go:build ignore + +// Builds and deploys the application to fly.io. +package main + +import ( + "context" + "os" + + "within.website/x/internal" + "within.website/x/internal/yeet" +) + +func main() { + internal.HandleStartup() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + env := append(os.Environ(), []string{"CGO_ENABLED=0", "GOOS=linux"}...) + yeet.ShouldWork(ctx, env, yeet.WD, "nix", "build", ".#robocadey2-docker") + yeet.DockerLoadResult(ctx, "./result") + yeet.DockerPush(ctx, "registry.fly.io/xe-robocadey2:latest") + yeet.ShouldWork(ctx, env, yeet.WD, "flyctl", "deploy", "--now") +} diff --git a/mastodon/robocadey2/fly.toml b/mastodon/robocadey2/fly.toml new file mode 100644 index 0000000..799ac1f --- /dev/null +++ b/mastodon/robocadey2/fly.toml @@ -0,0 +1,26 @@ +app = "xe-robocadey2" + +kill_signal = "SIGINT" +kill_timeout = 5 +processes = [] + +[deploy] + strategy = "immediate" + +[build] + image = "registry.fly.io/xe-robocadey2:latest" + +[env] + STATE = "/data/robocadey2" + +[experimental] + allowed_public_ports = [] + auto_rollback = true + +[mounts] + source="robocadey2_state" + destination="/data" + +[metrics] +port = 8080 +path = "/.within/metrics" diff --git a/mastodon/robocadey2/main.go b/mastodon/robocadey2/main.go index ce13a5c..07f9bbb 100644 --- a/mastodon/robocadey2/main.go +++ b/mastodon/robocadey2/main.go @@ -4,16 +4,24 @@ import ( "bytes" "context" "encoding/json" + "expvar" "flag" "fmt" + "io" + "log" "math/rand" + "net/http" "os" "path/filepath" "strings" "time" "github.com/jaytaylor/html2text" + "tailscale.com/metrics" + "tailscale.com/tsnet" + "tailscale.com/tsweb" "within.website/ln" + "within.website/ln/ex" "within.website/ln/opname" "within.website/x/internal" "within.website/x/internal/stablediffusion" @@ -21,13 +29,40 @@ import ( ) var ( + hostname = flag.String("hostname", "robocadey2", "hostname to use on tailnet") + dataDir = flag.String("dir", dataLocation(), "stateful data directory") instance = flag.String("instance", "", "mastodon instance") token = flag.String("token", "", "oauth2 token") + + uploads = expvar.NewInt("gauge_robocadey2_uploads") + retries = expvar.NewInt("gauge_robocadey2_retries") + + usageCount = metrics.LabelMap{Label: "user"} ) +func envOr(key, defaultVal string) string { + if result, ok := os.LookupEnv(key); ok { + return result + } + return defaultVal +} + +func dataLocation() string { + if dir, ok := os.LookupEnv("STATE"); ok { + return dir + } + dir, err := os.UserConfigDir() + if err != nil { + return os.Getenv("STATE") + } + return filepath.Join(dir, "within.website", "x", "robocadey2") +} + func main() { internal.HandleStartup() + os.MkdirAll(*dataDir, 0777) + ctx := opname.With(context.Background(), "main") rand.Seed(time.Now().Unix()) @@ -36,8 +71,44 @@ func main() { ln.FatalErr(ctx, err) } + expvar.Publish("gauge_robocadey_usage_by_user", &usageCount) + os.MkdirAll(filepath.Join(*dataDir, "tsnet"), 0777) + srv := &tsnet.Server{ + Hostname: *hostname, + Logf: log.New(io.Discard, "", 0).Printf, + AuthKey: os.Getenv("TS_AUTHKEY"), + Dir: filepath.Join(*dataDir, "tsnet"), + } + + if err := srv.Start(); err != nil { + ln.FatalErr(ctx, err) + } + + httpCli := srv.HTTPClient() + if err != nil { + ln.FatalErr(ctx, err) + } + ln.Log(ctx, ln.Info("waiting for messages")) + b := &Bot{ + cli: cli, + sd: &stablediffusion.Client{HTTP: httpCli}, + } + + go func() { + lis, err := srv.Listen("tcp", ":80") + if err != nil { + ln.FatalErr(ctx, err, ln.Action("tsnet listening")) + } + + http.DefaultServeMux.HandleFunc("/debug/varz", tsweb.VarzHandler) + + defer srv.Close() + defer lis.Close() + ln.FatalErr(opname.With(ctx, "metrics-tsnet"), http.Serve(lis, ex.HTTPLog(http.DefaultServeMux))) + }() + for { ctx, cancel := context.WithCancel(ctx) ch, err := cli.StreamMessages(ctx, mastodon.WSSubscribeRequest{Type: "subscribe", Stream: "user"}) @@ -54,7 +125,11 @@ func main() { continue } - if err := handleNotification(cli, n); err != nil { + if n.Type != "mention" { + continue + } + + if err := b.handleNotification(n); err != nil { ln.Error(ctx, err, ln.F{"content": n.Status.Content}) continue } @@ -64,7 +139,12 @@ func main() { } } -func handleNotification(c *mastodon.Client, n mastodon.Notification) error { +type Bot struct { + cli *mastodon.Client + sd *stablediffusion.Client +} + +func (b *Bot) handleNotification(n mastodon.Notification) error { text, err := html2text.FromString(n.Status.Content, html2text.Options{OmitLinks: true}) if err != nil { return nil @@ -77,14 +157,6 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { text = strings.TrimSpace(text) - fmt.Printf("text: %q\n", text) - - dir, err := os.MkdirTemp("", "robocadey2") - if err != nil { - return err - } - defer os.RemoveAll(dir) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() @@ -96,7 +168,7 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { extra = ", <lora:cdi:1>" } - imgs, err := stablediffusion.Generate(ctx, stablediffusion.SimpleImageRequest{ + imgs, err := b.sd.Generate(ctx, stablediffusion.SimpleImageRequest{ Prompt: "masterpiece, best quality, " + text + extra, NegativePrompt: "person in distance, worst quality, low quality, medium quality, deleted, lowres, comic, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, jpeg artifacts, signature, watermark, username, blurry", Seed: seed, @@ -114,10 +186,7 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { if err != nil { return err } - - if err := os.WriteFile(filepath.Join(dir, "result.png"), imgs.Images[0], 0600); err != nil { - return err - } + usageCount.Add(n.Status.Account.Acct, 1) response := &strings.Builder{} @@ -139,18 +208,21 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { tries := 4 for tries != 0 { - att, err = c.UploadMedia(ctx, bytes.NewBuffer(imgs.Images[0]), "result.png", "prompt: "+text, "") + att, err = b.cli.UploadMedia(ctx, bytes.NewBuffer(imgs.Images[0]), "result.png", "prompt: "+text, "") if err != nil { ln.Error(ctx, err, ln.F{"tries": tries}) time.Sleep(time.Second) + tries-- + retries.Add(1) continue } + uploads.Add(1) break } if tries == 0 { - c.CreateStatus(ctx, mastodon.CreateStatusParams{ - Status: response.String() + " @cadey please help: " + err.Error(), + b.cli.CreateStatus(ctx, mastodon.CreateStatusParams{ + Status: response.String() + " @cadey please help: " + err.Error() + " (tried 4 times)", Visibility: n.Status.Visibility, InReplyTo: n.Status.ID, }) @@ -161,7 +233,7 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { fmt.Fprintf(response, "seed: %d\n", seed) fmt.Fprintln(response, "Generated with #xediffusion early alpha") - if _, err := c.CreateStatus(ctx, mastodon.CreateStatusParams{ + if st, err := b.cli.CreateStatus(ctx, mastodon.CreateStatusParams{ Status: response.String(), MediaIDs: []string{att.ID}, SpoilerText: "AI generated image (can be NSFW)", @@ -169,6 +241,8 @@ func handleNotification(c *mastodon.Client, n mastodon.Notification) error { InReplyTo: n.Status.ID, }); err != nil { return err + } else { + ln.Log(ctx, ln.F{"url": st.URL, "responsible_party": n.Status.Account.Acct, "visibility": n.Status.Visibility}) } return nil diff --git a/web/mastodon/status.go b/web/mastodon/status.go index f3b8774..bdb0bbf 100644 --- a/web/mastodon/status.go +++ b/web/mastodon/status.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "io" - "log" "net/http" "net/url" "time" @@ -40,8 +39,6 @@ func (csp CreateStatusParams) Values() url.Values { result.Add("media_ids[]", id) } - log.Println(result.Encode()) - return result } |
