diff --git a/build.sh b/build.sh
index f8325fc714e4a96388df9023bd56e68cfffe4dfb..5a24787a4204c8cf8192c41269429c53193c8aee 100755
--- a/build.sh
+++ b/build.sh
@@ -1,5 +1,6 @@
 #!/bin/sh
 
+
 CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o darwin_arm64 -v .
 CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o darwin_amd64 -v .
 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o linux_amd64 -v .
diff --git a/main.go b/cmd/chat/main.go
similarity index 73%
rename from main.go
rename to cmd/chat/main.go
index 8e2db3a37dc96a71a2ff6a0dc81fc70354153fdd..7fe6f1572b40fd7a6a6a4844bd2543670e36d2f8 100644
--- a/main.go
+++ b/cmd/chat/main.go
@@ -1,6 +1,8 @@
 package main
 
-import "pp/web"
+import (
+	"pp/pkg/web"
+)
 
 const key = "#hashcode69"
 
diff --git a/cmd/grind/main.go b/cmd/grind/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..a17f846732d7f7ae273d4d7bce15797c5f5e20e3
--- /dev/null
+++ b/cmd/grind/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+	"pp/pkg/comms"
+	"pp/pkg/grind"
+	"time"
+)
+
+// // fos.Args[0] fd23::2:f083:f378:4f03 i
+
+// { "type": "discovered", "ips": [ "fd23::3:99d:951c:339b" ], "id": 69 }
+// mit szólsz ehhez? { "type": "discovered", "ips": [...], "id": 69696}, aztán {"type": "ack", "id": 6969} és {"type": "submitted", "text": "a!", "id": 420}
+
+var host = ""
+
+func main() {
+	time.Sleep(time.Second)
+
+	log.Println("Kakifantom is running")
+	host, _ = os.Hostname()
+	e := comms.Send("Kakifantom joined on " + host)
+	if e != nil {
+		panic(e)
+	}
+
+	for msg := range comms.Recv() {
+		var m grind.Message
+		e := json.Unmarshal([]byte(msg), &m)
+		if e != nil {
+			continue
+		}
+
+		switch m.Type {
+		case "discovered":
+			var d grind.Discovered
+			e = json.Unmarshal([]byte(msg), &d)
+			if e != nil {
+				log.Println(e)
+				continue
+			}
+			go func() {
+				e = grind.Discover(d)
+				if e != nil {
+					log.Println(e)
+				}
+			}()
+		case "submitted":
+			var s grind.Submitted
+			e = json.Unmarshal([]byte(msg), &s)
+			if e != nil {
+				log.Println(e)
+				continue
+			}
+			go func() {
+				e = grind.Submit(s)
+				if e != nil {
+					log.Println(e)
+				}
+			}()
+		case "ack":
+			log.Println("ack")
+
+		}
+	}
+}
diff --git a/cmd/scrape/main.go b/cmd/scrape/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..95a9f0613146fd6d2d79ff8b03617826b2da0081
--- /dev/null
+++ b/cmd/scrape/main.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"fmt"
+	"github.com/samber/lo"
+	"log"
+	"net"
+	"os"
+	"pp/pkg/comms"
+	"pp/pkg/tutter"
+	"regexp"
+	"strings"
+)
+
+var socket = "ws://127.0.0.1:8080/ws"
+
+func init() {
+	_socket := os.Getenv("SOCKET")
+	if _socket != "" {
+		socket = _socket
+	}
+}
+
+type Discovered struct {
+	Type string   `json:"type"`
+	Id   int      `json:"id"`
+	IPs  []string `json:"ips"`
+}
+
+const cookiecutter = `#important #addr
+New IPs just dropped! Get them while they're hot!`
+
+func main() {
+	for m := range tutter.LongPoll() {
+		if m.Author.Name == "ollescram" {
+			m.Text = string(lo.Reverse([]byte(m.Text)))
+		}
+		if strings.HasPrefix(m.Text, cookiecutter) {
+			continue
+		}
+
+		ips := ExtractIPv6Addresses(m.Text)
+		if len(ips) > 0 {
+			fmt.Println()
+			fmt.Println(m.Text)
+			fmt.Println(ips)
+			e := comms.SendJSON(Discovered{
+				Type: "discovered",
+				Id:   1,
+				IPs:  ips,
+			})
+			if e != nil {
+				log.Println(e)
+			}
+		}
+	}
+}
+
+// ExtractIPv6Addresses extracts all possible IPv6 addresses from a string.
+func ExtractIPv6Addresses(s string) []string {
+	// IPv6 addresses are typically represented in eight groups of four hexadecimal digits each, separated by colons.
+	// This regex will capture possible IPv6 addresses.
+	ipv6Regex := regexp.MustCompile(`([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}`)
+
+	// Find all matches in the input string.
+	matches := ipv6Regex.FindAllString(s, -1)
+
+	// Initialize an empty slice to hold the valid IPv6 addresses.
+	validIPv6Addresses := make([]string, 0)
+
+	// Iterate over the matches.
+	for _, match := range matches {
+		// Use net.ParseIP to validate the IP address.
+		// If it's a valid IPv6 address, it will be returned in IPv6 format.
+		// If it's not a valid IP address, or if it's an IPv4 address, nil will be returned.
+		if ip := net.ParseIP(match); ip != nil && strings.Contains(match, ":") {
+			validIPv6Addresses = append(validIPv6Addresses, match)
+		}
+	}
+
+	// Return the slice of valid IPv6 addresses.
+	return validIPv6Addresses
+}
diff --git a/last b/last
deleted file mode 100644
index 3b9e69dc7623fb7e61eed41b592c457366d28b2a..0000000000000000000000000000000000000000
--- a/last
+++ /dev/null
@@ -1 +0,0 @@
-122076
\ No newline at end of file
diff --git a/out/kakifantom b/out/kakifantom
index 8eab54443a0a7ee2a4e5a76f5799260e5c83341b..12c3e390c5305c65538a2ce744707611bdae7fde 100755
Binary files a/out/kakifantom and b/out/kakifantom differ
diff --git a/comms/key.go b/pkg/comms/key.go
similarity index 100%
rename from comms/key.go
rename to pkg/comms/key.go
diff --git a/comms/recv.go b/pkg/comms/recv.go
similarity index 97%
rename from comms/recv.go
rename to pkg/comms/recv.go
index 5d83b273630aa5fe81b060fbeedf3349e44d734d..b85a25c7618f057429b288da9a0bc91bd2850493 100644
--- a/comms/recv.go
+++ b/pkg/comms/recv.go
@@ -3,7 +3,7 @@ package comms
 import (
 	"encoding/json"
 	"github.com/funny/crypto/aes256cbc"
-	"pp/tutter"
+	"pp/pkg/tutter"
 	"strings"
 )
 
diff --git a/comms/send.go b/pkg/comms/send.go
similarity index 96%
rename from comms/send.go
rename to pkg/comms/send.go
index 9f96795ca1b9706e4b056326fbef81c0d94e1fa4..61862a4aaa3274e22b430967e3b76514b2a54f3d 100644
--- a/comms/send.go
+++ b/pkg/comms/send.go
@@ -5,7 +5,7 @@ import (
 	"github.com/funny/crypto/aes256cbc"
 	"github.com/samber/lo"
 	"math/rand"
-	"pp/tutter"
+	"pp/pkg/tutter"
 )
 
 func Send(msg string) error {
diff --git a/pkg/comms/sendJson.go b/pkg/comms/sendJson.go
new file mode 100644
index 0000000000000000000000000000000000000000..74378f9759c68e254cf16afcdd4bdfe4ee8b364a
--- /dev/null
+++ b/pkg/comms/sendJson.go
@@ -0,0 +1,12 @@
+package comms
+
+import "encoding/json"
+
+func SendJSON(thing any) error {
+	str, e := json.Marshal(thing)
+	if e != nil {
+		return e
+	}
+
+	return Send(string(str))
+}
diff --git a/comms/types.go b/pkg/comms/types.go
similarity index 100%
rename from comms/types.go
rename to pkg/comms/types.go
diff --git a/pkg/grind/discovered.go b/pkg/grind/discovered.go
new file mode 100644
index 0000000000000000000000000000000000000000..31b4987a83b55ded40f89071a8153bbd3e37ec08
--- /dev/null
+++ b/pkg/grind/discovered.go
@@ -0,0 +1,82 @@
+package grind
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"math/rand"
+	"net/http"
+	"strings"
+	"time"
+)
+
+type Details struct {
+	Score            int `json:"score"`
+	MaxBodySizeBytes int `json:"max_body_size_bytes"`
+	Properties       struct {
+		MinTokenLength       int `json:"min_token_length"`
+		MaxTokenLength       int `json:"max_token_length"`
+		TokenCharacterGroups struct {
+			AsciiLowercase bool `json:"ascii_lowercase"`
+			AsciiUppercase bool `json:"ascii_uppercase"`
+			Digits         bool `json:"digits"`
+			Punctuation    bool `json:"punctuation"`
+		} `json:"token_character_groups"`
+		SolvableUntilUnix int `json:"solvable_until_unix"`
+		InactivitySeconds int `json:"inactivity_seconds"`
+		MaxAttempts       int `json:"max_attempts"`
+	} `json:"properties"`
+}
+
+func Discover(d Discovered) error {
+	for _, ip := range d.IPs {
+		go func(ip string) {
+			log.Println("Discover", ip)
+
+			resp, e := http.Get(fmt.Sprintf("http://[%s]:8080/describe", ip))
+			if e != nil {
+				log.Println("host is ded", ip)
+				log.Println(e)
+				return
+			}
+
+			var det Details
+			dec := json.NewDecoder(resp.Body)
+			e = dec.Decode(&det)
+			if e != nil {
+				log.Println(e)
+				return
+			}
+
+			if det.Score < 0 {
+				log.Println("Shitty host detected")
+				return
+			}
+
+			str := ""
+			if det.Properties.TokenCharacterGroups.AsciiLowercase {
+				str = strings.Repeat("z", det.Properties.MaxTokenLength)
+			} else if det.Properties.TokenCharacterGroups.AsciiUppercase {
+				str = strings.Repeat("Z", det.Properties.MaxTokenLength)
+			} else if det.Properties.TokenCharacterGroups.Punctuation {
+				str = strings.Repeat(".", det.Properties.MaxTokenLength)
+			} else if det.Properties.TokenCharacterGroups.Digits {
+				str = strings.Repeat("9", det.Properties.MaxTokenLength)
+			}
+
+			_, e = submit(ip, str)
+			if e != nil {
+				log.Println(e)
+				return
+			}
+
+			if !nosleep {
+				t := time.Duration(rand.Intn(10)) * time.Second
+				fmt.Println("Sleeping for", t)
+				time.Sleep(t)
+			}
+		}(ip)
+		time.Sleep(time.Second / 2)
+	}
+	return nil
+}
diff --git a/pkg/grind/globals.go b/pkg/grind/globals.go
new file mode 100644
index 0000000000000000000000000000000000000000..1b2e8cc3b73933af0b0fadb1f5cc6434db520953
--- /dev/null
+++ b/pkg/grind/globals.go
@@ -0,0 +1,17 @@
+package grind
+
+import "os"
+
+var nosleep = false
+
+func init() {
+	if os.Getenv("NOSLEEP") == "true" {
+		nosleep = true
+	}
+}
+
+var host = ""
+
+func init() {
+	host, _ = os.Hostname()
+}
diff --git a/pkg/grind/sendMeme.go b/pkg/grind/sendMeme.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe62f5cefa29a2cd35f0480475458c2fb7adc2fe
--- /dev/null
+++ b/pkg/grind/sendMeme.go
@@ -0,0 +1,75 @@
+package grind
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/http"
+	"pp/pkg/comms"
+	"strings"
+	"sync"
+)
+
+var mut sync.Mutex
+var cache = make(map[string]bool)
+
+func submit(ip, pass string) ([]string, error) {
+	mut.Lock()
+	isCache := cache[ip]
+	mut.Unlock()
+	if isCache {
+		log.Println("Cache hit, deny to", ip)
+		return nil, nil
+	}
+
+	log.Println("Attempting", ip)
+	resp, e := http.Post(fmt.Sprintf("http://[%s]:8080/attempt", ip), "text/plain", strings.NewReader(pass+"\n"))
+	if e != nil {
+		log.Println("host is ded", ip)
+		return nil, e
+	}
+
+	switch resp.StatusCode {
+	case 400:
+		log.Println("Code wasn't accepted")
+	case 401:
+		log.Println("Oops, you haven't registered")
+	case 409:
+		log.Println("Race condition, we'll get 'em next time")
+	case 500:
+		log.Println("They fucked up")
+	case 204:
+		log.Printf("First submission to %s\n", ip)
+		mut.Lock()
+		cache[ip] = true
+		mut.Unlock()
+		e = comms.SendJSON(Submitted{
+			Id:       0,
+			IP:       ip,
+			Type:     "submitted",
+			Text:     pass,
+			Hostname: host,
+		})
+		return nil, e
+	case 200:
+		var ips []string
+		dec := json.NewDecoder(resp.Body)
+		e = dec.Decode(&ips)
+		if e != nil {
+			return nil, e
+		}
+
+		log.Printf("Succesful solve of %s\n", ip)
+		defer resp.Body.Close()
+
+		e = comms.SendJSON(Discovered{
+			Type:     "discovered",
+			Id:       0,
+			IPs:      ips,
+			Hostname: host,
+		})
+
+		return ips, e
+	}
+	return nil, nil
+}
diff --git a/pkg/grind/submitted.go b/pkg/grind/submitted.go
new file mode 100644
index 0000000000000000000000000000000000000000..5e93a281c76b90b211537f4283efb737639f6459
--- /dev/null
+++ b/pkg/grind/submitted.go
@@ -0,0 +1,22 @@
+package grind
+
+import (
+	"fmt"
+	"log"
+	"math/rand"
+	"time"
+)
+
+func Submit(s Submitted) error {
+	if !nosleep {
+		t := time.Duration(rand.Intn(10)) * time.Second
+		fmt.Println("Sleeping for", t)
+		time.Sleep(t)
+	}
+
+	log.Println("Submit", s.IP)
+
+	_, e := submit(s.IP, s.Text)
+
+	return e
+}
diff --git a/pkg/grind/type.go b/pkg/grind/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc931063f5c15036643e818237799b4f41fe603d
--- /dev/null
+++ b/pkg/grind/type.go
@@ -0,0 +1,21 @@
+package grind
+
+type Message struct {
+	Type string `json:"type"`
+	Id   int    `json:"id"`
+}
+
+type Discovered struct {
+	Type     string   `json:"type"`
+	Id       int      `json:"id"`
+	IPs      []string `json:"ips"`
+	Hostname string   `json:"hostname"`
+}
+
+type Submitted struct {
+	Type     string `json:"type"`
+	Id       int    `json:"id"`
+	IP       string `json:"IP"`
+	Text     string `json:"text"`
+	Hostname string `json:"hostname"`
+}
diff --git a/tutter/tutter.go b/pkg/tutter/tutter.go
similarity index 73%
rename from tutter/tutter.go
rename to pkg/tutter/tutter.go
index 2f38d3f1bcea0db8948fb145744c9ceea28eab47..26f88ae9727a7bff602c875cdaaf4c6faab9e05d 100644
--- a/tutter/tutter.go
+++ b/pkg/tutter/tutter.go
@@ -8,8 +8,6 @@ import (
 	"log"
 	"net/http"
 	"os"
-	"strconv"
-	"strings"
 	"time"
 )
 
@@ -29,35 +27,21 @@ const url = "https://tutter.pproj.dev/api/poll"
 
 var last = 0
 
-func readLast() {
-	by, e := os.ReadFile("last")
-	if e != nil {
-		panic(e)
-	}
-
-	_last, e := strconv.Atoi(strings.TrimSpace(string(by)))
-	if e != nil {
-		panic(e)
-	}
-	last = _last
-}
-
-func init() {
-	readLast()
-}
-
 func LongPoll() <-chan Message {
 	ch := make(chan Message)
 
 	go func() {
 		for {
-			murl := fmt.Sprintf("%s?last=%d", url, last)
+			murl := "https://tutter.pproj.dev/api/post?limit=25&order=desc"
+			if last != 0 {
+				murl = fmt.Sprintf("%s?last=%d", url, last)
+			}
+
 			resp, e := http.Get(murl)
 			if e != nil {
 				log.Println(e)
 				continue
 			}
-
 			var messages []Message
 
 			js := json.NewDecoder(resp.Body)
@@ -70,16 +54,6 @@ func LongPoll() <-chan Message {
 			lo.ForEach(messages, func(msg Message, _ int) {
 				ch <- msg
 			})
-
-			ids := lo.Map(messages, func(item Message, _ int) int {
-				return item.Id
-			})
-
-			last = lo.Max(ids)
-			e = os.WriteFile("last", []byte(strconv.Itoa(last)), os.ModePerm)
-			if e != nil {
-				log.Println(e)
-			}
 		}
 	}()
 
diff --git a/web/chat.html b/pkg/web/chat.html
similarity index 100%
rename from web/chat.html
rename to pkg/web/chat.html
diff --git a/web/web.go b/pkg/web/web.go
similarity index 93%
rename from web/web.go
rename to pkg/web/web.go
index 0f5339db57d24a233d53d50c3091b37be774f7c7..c0e6ad0d5714f386842a1b4ada6eea31bb31e35b 100644
--- a/web/web.go
+++ b/pkg/web/web.go
@@ -6,7 +6,7 @@ import (
 	"html/template"
 	"net/http"
 	"os"
-	"pp/comms"
+	comms2 "pp/pkg/comms"
 	"strings"
 	"time"
 )
@@ -46,7 +46,7 @@ func Start() {
 		if msg == "" {
 			return
 		}
-		e := comms.Send(msg)
+		e := comms2.Send(msg)
 		if e != nil {
 			c.Error(e)
 			return
@@ -78,7 +78,7 @@ func reverse(sl []string) []string {
 
 func listenForMsg() {
 	go func() {
-		for c := range comms.Recv() {
+		for c := range comms2.Recv() {
 			messages = append(messages, c)
 			SendMsg(c)
 		}
diff --git a/web/ws.go b/pkg/web/ws.go
similarity index 97%
rename from web/ws.go
rename to pkg/web/ws.go
index 2b5338a93359c553df4fc5f7246330143c16fe45..d2362da132bf782bc3285a9da5148bfbd6ecda6a 100644
--- a/web/ws.go
+++ b/pkg/web/ws.go
@@ -6,7 +6,7 @@ import (
 	"github.com/gorilla/websocket"
 	"log"
 	"net/http"
-	"pp/comms"
+	"pp/pkg/comms"
 )
 
 var sockets = map[*websocket.Conn]bool{}