From fe282d2e7720b100b8a7ea7784f97039a1a8512f Mon Sep 17 00:00:00 2001 From: Purrfxct Date: Sat, 25 Jan 2025 20:30:30 +0000 Subject: [PATCH] lets go pikachu --- dev.go | 264 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 dev.go diff --git a/dev.go b/dev.go new file mode 100644 index 0000000..98f6c13 --- /dev/null +++ b/dev.go @@ -0,0 +1,264 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "math/rand" + "net" + "net/http" + "os" + "strings" + "sync" + "time" + "io" + + "golang.org/x/net/html" +) + +type ScanState struct { + ValidInviteCount int `json:"valid_invite_count"` + ScannedInviteCount int `json:"scanned_invite_count"` + ErrorCount int `json:"error_count"` + StartTime time.Time `json:"-"` + ValidServers []ValidServerInfo `json:"valid_servers"` +} + +type ValidServerInfo struct { + InviteURL string `json:"invite_url"` + ServerName string `json:"server_name"` + Members int `json:"members"` +} + +var ( + server = "irc.supernets.org" + port = "6667" + channel = "#superbowl" + outputChannel = "#guttertarts" + nickname = "x" + nickservPassword = "x" + + httpClient = &http.Client{Timeout: 10 * time.Second} + state ScanState + stateMutex sync.Mutex + inviteLog *os.File +) + +func handleIRCConnection(conn net.Conn, connected chan bool) { + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, ":End of /MOTD command.") { + connected <- true + } + if strings.HasPrefix(line, "PING") { + pongResponse := strings.Split(line, " ")[1] + ircSend(conn, fmt.Sprintf("PONG %s", pongResponse)) + } + } + if err := scanner.Err(); err != nil { + fmt.Printf("Connection error: %v\n", err) + } +} + +func checkInvite(url string, conn net.Conn) { + stateMutex.Lock() + state.ScannedInviteCount++ + stateMutex.Unlock() + + resp, err := httpClient.Get(url) + if err != nil { + stateMutex.Lock() + state.ErrorCount++ + stateMutex.Unlock() + fmt.Printf("Error fetching invite: %v\n", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + serverName, members, isValid := parseDiscordHTML(resp.Body) + if isValid { + stateMutex.Lock() + state.ValidInviteCount++ + state.ValidServers = append(state.ValidServers, ValidServerInfo{ + InviteURL: url, + ServerName: serverName, + Members: members, + }) + stateMutex.Unlock() + + logMessage := fmt.Sprintf("Valid invite: %s | Server: %s | Members: %d\n", + url, serverName, members) + ircMessage := fmt.Sprintf("PRIVMSG %s :Valid invite: %s | Server: %s | Members: %d", + outputChannel, url, serverName, members) + + fmt.Fprint(inviteLog, logMessage) + ircSend(conn, ircMessage) + } else { + fmt.Printf("Invalid invite (no 'Join the' phrase): %s\n", url) + } + } else { + fmt.Printf("Invalid invite: %s\n", url) + } +} + +func parseDiscordHTML(bodyReader io.Reader) (string, int, bool) { + doc, err := html.Parse(bodyReader) + if err != nil { + fmt.Printf("Error parsing HTML: %v\n", err) + return "", 0, false + } + + var serverName string + var members int + var hasJoinThe bool + + var parse func(*html.Node) + parse = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "meta" { + var property, name, content string + for _, attr := range n.Attr { + if attr.Key == "property" { + property = attr.Val + } else if attr.Key == "name" { + name = attr.Val + } else if attr.Key == "content" { + content = attr.Val + } + } + // Check for server name in og:title + if property == "og:title" { + serverName = strings.TrimSpace(strings.Replace(content, "Join the", "", 1)) + serverName = strings.Replace(serverName, "Discord Server!", "", 1) + serverName = strings.TrimSpace(serverName) + } + // Check for member count in description + if name == "description" { + if strings.Contains(content, "members") { + parts := strings.Split(content, "|") + for _, part := range parts { + part = strings.TrimSpace(part) + if strings.HasSuffix(part, "members") { + fmt.Sscanf(part, "%d members", &members) + break + } + } + } else if strings.Contains(content, "other members") { + fmt.Sscanf(content, "%d other members", &members) + } + } + // Check for "Join the" phrase + if strings.Contains(content, "Join the") { + hasJoinThe = true + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + parse(c) + } + } + + parse(doc) + return serverName, members, hasJoinThe +} + +func ircSend(conn net.Conn, message string) { + fmt.Fprintf(conn, "%s\r\n", message) + fmt.Printf("[IRC Command Sent]: %s\n", message) +} + +func generateRandomCode() string { + length := rand.Intn(9) + 2 + letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + code := make([]rune, length) + for i := range code { + code[i] = letters[rand.Intn(len(letters))] + } + return string(code) +} + +func startWebDashboard() { + http.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { + stateMutex.Lock() + defer stateMutex.Unlock() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(state) + }) + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + stateMutex.Lock() + defer stateMutex.Unlock() + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Invite Scanner") + fmt.Fprintf(w, "

Discord Invite Scanner Dashboard

") + fmt.Fprintf(w, "") + fmt.Fprintf(w, "") + }) + + fmt.Println("Web dashboard running on http://localhost:55555") + http.ListenAndServe(":55555", nil) +} + +func main() { + var err error + inviteLog, err = os.OpenFile("valid_invites.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + fmt.Printf("Error opening log file: %v\n", err) + return + } + defer inviteLog.Close() + + conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", server, port)) + if err != nil { + fmt.Printf("Error connecting to IRC server: %v\n", err) + return + } + defer conn.Close() + + connected := make(chan bool) + go handleIRCConnection(conn, connected) + + ircSend(conn, fmt.Sprintf("NICK %s", nickname)) + ircSend(conn, fmt.Sprintf("USER %s 0 * :%s", nickname, nickname)) + <-connected + + ircSend(conn, fmt.Sprintf("PRIVMSG NickServ :IDENTIFY %s", nickservPassword)) + time.Sleep(5 * time.Second) + ircSend(conn, fmt.Sprintf("JOIN %s", channel)) + time.Sleep(5 * time.Second) + ircSend(conn, fmt.Sprintf("JOIN %s", outputChannel)) + + state.StartTime = time.Now() + + go startWebDashboard() + + ticker := time.NewTicker(10 * time.Minute) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + duration := time.Since(state.StartTime) + statusMessage := fmt.Sprintf( + "Status: Time Scanning: %s | Invites Scanned: %d | Valid Invites: %d", + duration.Truncate(time.Second), state.ScannedInviteCount, state.ValidInviteCount, + ) + ircSend(conn, fmt.Sprintf("PRIVMSG %s :%s", channel, statusMessage)) + case <-time.After(time.Duration(rand.Intn(30)+1) * time.Second): + randomCode := generateRandomCode() + inviteURL := fmt.Sprintf("https://discord.com/invite/%s", randomCode) + checkInvite(inviteURL, conn) + } + } +}