diff --git a/scripts/generate_deep_thread.go b/scripts/generate_deep_thread.go new file mode 100644 --- /dev/null +++ b/scripts/generate_deep_thread.go @@ -0,0 +1,218 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "math/rand" + "time" + + _ "github.com/lib/pq" +) + +const ( + postURI = "at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m56mowhbuk22" + postCID = "bafyreibml4midgt7ojq7dnabnku5ikzro4erfvdux6mmiqeat7pci2gy4u" + communityDID = "did:plc:hcuo3qx2lr7h7dquusbeobht" +) + +type User struct { + DID string + Handle string + Name string +} + +type Comment struct { + URI string + CID string + RKey string + DID string + RootURI string + RootCID string + ParentURI string + ParentCID string + Content string + CreatedAt time.Time +} + +// Escalating conversation between two users +var deepThreadConversation = []string{ + "Wait, I just realized - if they both get suspended for this, their fantasy managers are SCREWED 😂", + "Bro imagine being in a league where you have BOTH Duren brothers and they both get suspended for fighting EACH OTHER", + "That's actually hilarious. 'Dear commissioner, my players got suspended for fighting... with each other'", + "The fantasy implications are wild. Do you get negative points for your players fighting your other players? 🤔", + "New fantasy category: Family Feuds. Duren brothers leading the league in FFD (Family Fight Disqualifications)", + "I'm dying 💀 FFD should absolutely be a stat. The Morris twins would've been unstoppable in that category", + "Don't forget the Plumlees! Those boys used to scrap in college practices. FFD Hall of Famers", + "Okay but serious question: has there EVER been brothers fighting each other in an NBA game before this? This has to be a first", + "I've been watching the NBA for 30 years and I can't think of a single time. This might genuinely be historic family beef", + "So we're witnessing NBA history right now. Not the good kind, but history nonetheless. Their mom is SO proud 😂", +} + +var userHandles = []string{ + "deep_thread_guy_1.bsky.social", + "deep_thread_guy_2.bsky.social", +} + +func generateTID() string { + now := time.Now().UnixMicro() + return fmt.Sprintf("%d%04d", now, rand.Intn(10000)) +} + +func createUser(db *sql.DB, handle string, idx int) (*User, error) { + did := fmt.Sprintf("did:plc:deepthread%d%d", time.Now().Unix(), idx) + user := &User{ + DID: did, + Handle: handle, + Name: handle, + } + + query := ` + INSERT INTO users (did, handle, pds_url, created_at, updated_at) + VALUES ($1, $2, $3, NOW(), NOW()) + ON CONFLICT (did) DO NOTHING + ` + + _, err := db.Exec(query, user.DID, user.Handle, "http://localhost:3001") + if err != nil { + return nil, fmt.Errorf("failed to create user: %w", err) + } + + log.Printf("Created user: %s (%s)", user.Handle, user.DID) + return user, nil +} + +func createComment(db *sql.DB, user *User, content, parentURI, parentCID string, createdAt time.Time) (*Comment, error) { + rkey := generateTID() + uri := fmt.Sprintf("at://%s/social.coves.feed.comment/%s", user.DID, rkey) + cid := fmt.Sprintf("bafy%s", rkey) + + comment := &Comment{ + URI: uri, + CID: cid, + RKey: rkey, + DID: user.DID, + RootURI: postURI, + RootCID: postCID, + ParentURI: parentURI, + ParentCID: parentCID, + Content: content, + CreatedAt: createdAt, + } + + query := ` + INSERT INTO comments ( + uri, cid, rkey, commenter_did, root_uri, root_cid, + parent_uri, parent_cid, content, created_at, indexed_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW()) + ON CONFLICT (uri) DO NOTHING + RETURNING id + ` + + var id int64 + err := db.QueryRow(query, + comment.URI, comment.CID, comment.RKey, comment.DID, + comment.RootURI, comment.RootCID, comment.ParentURI, comment.ParentCID, + comment.Content, comment.CreatedAt, + ).Scan(&id) + if err != nil { + return nil, fmt.Errorf("failed to create comment: %w", err) + } + + log.Printf("Level %d: %s", getCurrentLevel(parentURI), content) + return comment, nil +} + +func getCurrentLevel(parentURI string) int { + if parentURI == postURI { + return 1 + } + // Count how many times we've nested (rough estimate) + return 2 // Will be incremented as we go +} + +func updateCommentCount(db *sql.DB, parentURI string, isPost bool) error { + if isPost { + _, err := db.Exec(` + UPDATE posts + SET comment_count = comment_count + 1 + WHERE uri = $1 + `, parentURI) + return err + } + + _, err := db.Exec(` + UPDATE comments + SET reply_count = reply_count + 1 + WHERE uri = $1 + `, parentURI) + return err +} + +func main() { + dbURL := "postgres://dev_user:dev_password@localhost:5435/coves_dev?sslmode=disable" + db, err := sql.Open("postgres", dbURL) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + defer db.Close() + + if err := db.Ping(); err != nil { + log.Fatalf("Failed to ping database: %v", err) + } + + log.Println("Connected to database successfully!") + log.Println("Creating 10-level deep comment thread...") + + rand.Seed(time.Now().UnixNano()) + + // Create two users who will have the back-and-forth + user1, err := createUser(db, userHandles[0], 1) + if err != nil { + log.Fatalf("Failed to create user 1: %v", err) + } + + user2, err := createUser(db, userHandles[1], 2) + if err != nil { + log.Fatalf("Failed to create user 2: %v", err) + } + + baseTime := time.Now().Add(-30 * time.Minute) + + // Create the 10-level deep thread + parentURI := postURI + parentCID := postCID + isPost := true + + for i, content := range deepThreadConversation { + // Alternate between users + user := user1 + if i%2 == 1 { + user = user2 + } + + createdAt := baseTime.Add(time.Duration(i*2) * time.Minute) + + comment, err := createComment(db, user, content, parentURI, parentCID, createdAt) + if err != nil { + log.Fatalf("Failed to create comment at level %d: %v", i+1, err) + } + + // Update parent's reply count + if err := updateCommentCount(db, parentURI, isPost); err != nil { + log.Printf("Warning: Failed to update comment count: %v", err) + } + + // Set this comment as the parent for the next iteration + parentURI = comment.URI + parentCID = comment.CID + isPost = false + + time.Sleep(10 * time.Millisecond) + } + + log.Println("\n=== Summary ===") + log.Printf("Created 10-level deep comment thread") + log.Printf("Thread participants: %s and %s", user1.Handle, user2.Handle) + log.Println("Done! Check the NBACentral post for the deep thread.") +} diff --git a/scripts/generate_nba_comments.go b/scripts/generate_nba_comments.go new file mode 100644 --- /dev/null +++ b/scripts/generate_nba_comments.go @@ -0,0 +1,352 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "math/rand" + "time" + + _ "github.com/lib/pq" +) + +// Post URI for "Your son don't wanna be here..." NBACentral post +// at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m56mowhbuk22 + +const ( + postURI = "at://did:plc:hcuo3qx2lr7h7dquusbeobht/social.coves.community.post/3m56mowhbuk22" + postCID = "bafyreibml4midgt7ojq7dnabnku5ikzro4erfvdux6mmiqeat7pci2gy4u" + communityDID = "did:plc:hcuo3qx2lr7h7dquusbeobht" +) + +type User struct { + DID string + Handle string + Name string +} + +type Comment struct { + URI string + CID string + RKey string + DID string + RootURI string + RootCID string + ParentURI string + ParentCID string + Content string + CreatedAt time.Time +} + +var userNames = []string{ + "lakers_fan_23", "pistons_nation", "nba_historian", "hoops_enthusiast", + "detroit_pride", "basketball_iq", "courtside_view", "rim_protector", + "three_point_specialist", "paint_beast", "fast_break_fan", "clutch_time", + "triple_double_king", "defense_wins", "small_ball_era", "old_school_hoops", + "draft_expert", "salary_cap_guru", "trade_machine", "basketball_analytics", + "box_score_reader", "eye_test_guy", "film_room_analyst", "player_development", + "hometown_hero", "bandwagon_fan", "loyal_since_day_one", "casual_viewer", + "die_hard_supporter", "armchair_coach", "nbatv_addict", "league_pass_subscriber", +} + +var topLevelComments = []string{ + "Imagine having to explain to your mom at Thanksgiving that you got ejected for fighting your brother 💀", + "Mrs. Duren watching this at home like 'I didn't raise y'all like this'", + "Their mom is somewhere absolutely LIVID right now. Both of them getting the belt when they get home", + "This is the most expensive sibling rivalry in history lmao", + "Jalen really said 'I've been whooping your ass since we were kids, what makes you think tonight's different' 😂", + "Ausar thought the NBA would protect him from his older brother. He thought wrong.", + "The trash talk must have been PERSONAL. That's years of sibling beef coming out", + "Family group chat is gonna be awkward after this one", + "Their parents spent 18 years breaking up fights just for it to happen on national TV", + "This is what happens when little bro thinks he's tough now that he's in the league", + "Jalen's been dunking on Ausar in the driveway for years, this was just another Tuesday for him", + "The fact that they're both in the league and THIS is how they settle it 💀💀💀", + "Ausar: 'I'm in the NBA now, I'm not scared of you anymore' - Jalen: 'BET'", + "Mom definitely called both of them after the game. Neither one answered lol", + "This is the content I pay League Pass for. Brothers getting into it on the court is peak entertainment", + "Thanksgiving dinner is about to be TENSE in the Duren/Thompson household", + "Little brother energy vs Big brother authority. Tale as old as time", + "The refs trying to break them up like 'Sir that's your BROTHER'", + "Jalen been waiting for this moment since Ausar got drafted", + "Both of them getting fined and their mom making them split the cost 😂", + "This brings me back to fighting my brother over the last piece of pizza. Just at a much higher tax bracket", + "The Pistons and Rockets staff trying to separate them: 'Guys we have practice tomorrow!'", + "Ausar finally tall enough to talk back and chose violence", + "Their dad watching like 'At least wait til you're both All-Stars before embarrassing the family'", + "This is what decades of 'Mom said it's my turn on the Xbox' leads to", +} + +var replyComments = []string{ + "LMAOOO facts, mom's not playing", + "Bro I'm crying at this visual 😂😂😂", + "This is the one right here 💀", + "Thanksgiving about to be SILENT", + "You know their dad had flashbacks to breaking up driveway fights", + "The family group chat IS ON FIRE right now I guarantee it", + "Little bro syndrome is real and Ausar has it BAD", + "Big facts. Jalen been the big brother his whole life, NBA don't change that", + "Mom's gonna make them hug it out before Christmas I'm calling it now", + "This comment wins 😂😂😂", + "I need the full footage of what was said because it had to be PERSONAL", + "Years of sibling rivalry just exploded on NBA hardwood", + "The refs were so confused trying to separate family members 💀", + "Both of them getting the 'I'm disappointed' text from mom", + "Ausar thought NBA money meant he was safe. Nope.", + "Jalen's been waiting to humble him since draft night", + "This is exactly what their parents warned them about lmao", + "The fine money coming out of their allowance fr fr", + "Peak sibling behavior. I respect it.", + "Someone check on Mrs. Duren she's probably stress eating rn", +} + +var deepReplyComments = []string{ + "And you KNOW mom's taking both their sides AND neither side at the same time", + "Family dynamics don't stop just cause you're making millions. Big brother gonna big brother", + "This thread has me in TEARS. Y'all are hilarious 😭", + "The fact that NBA refs had to break up a family dispute is sending me", + "Both of them are gonna act like nothing happened next family reunion", + "I guarantee their teammates are ROASTING them in the group chats right now", + "This is the most relatable NBA drama I've ever seen. We all fought our siblings", + "Mom's calling BOTH coaches after this I just know it", + "The league office trying to figure out how to fine siblings for fighting each other", + "This is gonna be an amazing 30 for 30 one day: 'What if I told you family and basketball don't always mix'", +} + +func generateTID() string { + // Simple TID generator for testing (timestamp in microseconds + random) + now := time.Now().UnixMicro() + return fmt.Sprintf("%d%04d", now, rand.Intn(10000)) +} + +func createUser(db *sql.DB, handle, name string, idx int) (*User, error) { + did := fmt.Sprintf("did:plc:testuser%d%d", time.Now().Unix(), idx) + user := &User{ + DID: did, + Handle: handle, + Name: name, + } + + query := ` + INSERT INTO users (did, handle, pds_url, created_at, updated_at) + VALUES ($1, $2, $3, NOW(), NOW()) + ON CONFLICT (did) DO NOTHING + ` + + _, err := db.Exec(query, user.DID, user.Handle, "http://localhost:3001") + if err != nil { + return nil, fmt.Errorf("failed to create user: %w", err) + } + + log.Printf("Created user: %s (%s)", user.Handle, user.DID) + return user, nil +} + +func createComment(db *sql.DB, user *User, content, parentURI, parentCID string, createdAt time.Time) (*Comment, error) { + rkey := generateTID() + uri := fmt.Sprintf("at://%s/social.coves.feed.comment/%s", user.DID, rkey) + cid := fmt.Sprintf("bafy%s", rkey) + + comment := &Comment{ + URI: uri, + CID: cid, + RKey: rkey, + DID: user.DID, + RootURI: postURI, + RootCID: postCID, + ParentURI: parentURI, + ParentCID: parentCID, + Content: content, + CreatedAt: createdAt, + } + + query := ` + INSERT INTO comments ( + uri, cid, rkey, commenter_did, root_uri, root_cid, + parent_uri, parent_cid, content, created_at, indexed_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW()) + ON CONFLICT (uri) DO NOTHING + RETURNING id + ` + + var id int64 + err := db.QueryRow(query, + comment.URI, comment.CID, comment.RKey, comment.DID, + comment.RootURI, comment.RootCID, comment.ParentURI, comment.ParentCID, + comment.Content, comment.CreatedAt, + ).Scan(&id) + if err != nil { + return nil, fmt.Errorf("failed to create comment: %w", err) + } + + log.Printf("Created comment by %s: %.50s...", user.Handle, content) + return comment, nil +} + +func updateCommentCount(db *sql.DB, parentURI string, isPost bool) error { + if isPost { + _, err := db.Exec(` + UPDATE posts + SET comment_count = comment_count + 1 + WHERE uri = $1 + `, parentURI) + return err + } + + _, err := db.Exec(` + UPDATE comments + SET reply_count = reply_count + 1 + WHERE uri = $1 + `, parentURI) + return err +} + +func main() { + // Connect to dev database + dbURL := "postgres://dev_user:dev_password@localhost:5435/coves_dev?sslmode=disable" + db, err := sql.Open("postgres", dbURL) + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + defer db.Close() + + if err := db.Ping(); err != nil { + log.Fatalf("Failed to ping database: %v", err) + } + + log.Println("Connected to database successfully!") + log.Printf("Post URI: %s", postURI) + log.Println("Starting to generate NBA test comments...") + + rand.Seed(time.Now().UnixNano()) + + // Create users + log.Println("\n=== Creating Users ===") + users := make([]*User, 0, len(userNames)) + for i, name := range userNames { + handle := fmt.Sprintf("%s.bsky.social", name) + user, err := createUser(db, handle, name, i) + if err != nil { + log.Printf("Warning: Failed to create user %s: %v", name, err) + continue + } + users = append(users, user) + } + + log.Printf("\nCreated %d users", len(users)) + + // Generate comments with varied timing + log.Println("\n=== Creating Top-Level Comments ===") + baseTime := time.Now().Add(-3 * time.Hour) // Comments from 3 hours ago + topLevelCommentsCreated := make([]*Comment, 0) + + // Create 18-22 top-level comments + numTopLevel := 18 + rand.Intn(5) + for i := 0; i < numTopLevel && i < len(users) && i < len(topLevelComments); i++ { + user := users[i] + content := topLevelComments[i] + createdAt := baseTime.Add(time.Duration(i*4+rand.Intn(3)) * time.Minute) + + comment, err := createComment(db, user, content, postURI, postCID, createdAt) + if err != nil { + log.Printf("Warning: Failed to create top-level comment: %v", err) + continue + } + + topLevelCommentsCreated = append(topLevelCommentsCreated, comment) + + // Update post comment count + if err := updateCommentCount(db, postURI, true); err != nil { + log.Printf("Warning: Failed to update post comment count: %v", err) + } + + // Small delay to avoid timestamp collisions + time.Sleep(10 * time.Millisecond) + } + + log.Printf("Created %d top-level comments", len(topLevelCommentsCreated)) + + // Create first-level replies (replies to top-level comments) + log.Println("\n=== Creating First-Level Replies ===") + firstLevelReplies := make([]*Comment, 0) + + for i, parentComment := range topLevelCommentsCreated { + // 70% chance of having replies (NBA threads get lots of engagement) + if rand.Float64() > 0.7 { + continue + } + + // 1-4 replies per comment + numReplies := 1 + rand.Intn(4) + for j := 0; j < numReplies && len(replyComments) > 0; j++ { + userIdx := (i*3 + j + len(topLevelCommentsCreated)) % len(users) + user := users[userIdx] + content := replyComments[rand.Intn(len(replyComments))] + createdAt := parentComment.CreatedAt.Add(time.Duration(3+rand.Intn(8)) * time.Minute) + + comment, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt) + if err != nil { + log.Printf("Warning: Failed to create first-level reply: %v", err) + continue + } + + firstLevelReplies = append(firstLevelReplies, comment) + + // Update parent comment reply count + if err := updateCommentCount(db, parentComment.URI, false); err != nil { + log.Printf("Warning: Failed to update comment reply count: %v", err) + } + + time.Sleep(10 * time.Millisecond) + } + } + + log.Printf("Created %d first-level replies", len(firstLevelReplies)) + + // Create second-level replies (replies to replies) - deep threads + log.Println("\n=== Creating Second-Level Replies ===") + secondLevelCount := 0 + + for i, parentComment := range firstLevelReplies { + // 50% chance of having deep replies (NBA drama threads go DEEP) + if rand.Float64() > 0.5 { + continue + } + + // 1-2 deep replies + numReplies := 1 + rand.Intn(2) + for j := 0; j < numReplies && len(deepReplyComments) > 0; j++ { + userIdx := (i*2 + j + len(topLevelCommentsCreated) + len(firstLevelReplies)) % len(users) + user := users[userIdx] + content := deepReplyComments[rand.Intn(len(deepReplyComments))] + createdAt := parentComment.CreatedAt.Add(time.Duration(2+rand.Intn(5)) * time.Minute) + + _, err := createComment(db, user, content, parentComment.URI, parentComment.CID, createdAt) + if err != nil { + log.Printf("Warning: Failed to create second-level reply: %v", err) + continue + } + + secondLevelCount++ + + // Update parent comment reply count + if err := updateCommentCount(db, parentComment.URI, false); err != nil { + log.Printf("Warning: Failed to update comment reply count: %v", err) + } + + time.Sleep(10 * time.Millisecond) + } + } + + log.Printf("Created %d second-level replies", secondLevelCount) + + // Print summary + totalComments := len(topLevelCommentsCreated) + len(firstLevelReplies) + secondLevelCount + log.Println("\n=== Summary ===") + log.Printf("Total users created: %d", len(users)) + log.Printf("Total comments created: %d", totalComments) + log.Printf(" - Top-level comments: %d", len(topLevelCommentsCreated)) + log.Printf(" - First-level replies: %d", len(firstLevelReplies)) + log.Printf(" - Second-level replies: %d", secondLevelCount) + log.Println("\nDone! Check the NBACentral post for the brothers drama comments.") +}