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, "