aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-02-04 18:36:39 -0500
committerXe Iaso <me@xeiaso.net>2023-02-04 18:36:39 -0500
commit2ef631b425070fd3d9a0581ee9f77f563edebe20 (patch)
tree2f1887937da8eb5b2b5a16fb3ea122c6b0da3c0a
parent1d2bcebb5289372015967aaff7e6cf7029e0c7c6 (diff)
downloadx-2ef631b425070fd3d9a0581ee9f77f563edebe20.tar.xz
x-2ef631b425070fd3d9a0581ee9f77f563edebe20.zip
mastodon/robocadey2: contact logos over tsnet
Signed-off-by: Xe Iaso <me@xeiaso.net>
-rw-r--r--flake.nix22
-rw-r--r--internal/stablediffusion/stablediffusion.go16
-rw-r--r--mastodon/robocadey2/build.go25
-rw-r--r--mastodon/robocadey2/fly.toml26
-rw-r--r--mastodon/robocadey2/main.go112
-rw-r--r--web/mastodon/status.go3
6 files changed, 179 insertions, 25 deletions
diff --git a/flake.nix b/flake.nix
index 41edef4..6232580 100644
--- a/flake.nix
+++ b/flake.nix
@@ -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
}