Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop Product Hunt Parser #15

Closed
Sddilora opened this issue May 12, 2024 · 3 comments
Closed

Develop Product Hunt Parser #15

Sddilora opened this issue May 12, 2024 · 3 comments
Assignees
Labels
Be Be Team research Investigation or exploration of new concepts, techniques, or approaches web scrap Web scraping is required

Comments

@Sddilora
Copy link
Member

Sddilora commented May 12, 2024

Create a script to parse Product Hunt data in response to a new command (/productHuntStar) where a user can input a Product Hunt link. The script should retrieve information on who from our team has starred the product (which also requires retrieving our team's Product Hunt accounts as prerequisities), the total number of stars, and other relevant details.

@octopos-prime octopos-prime added the Be Be Team label May 12, 2024
@Sddilora Sddilora added web scrap Web scraping is required research Investigation or exploration of new concepts, techniques, or approaches labels May 15, 2024
@Sddilora
Copy link
Member Author

Product Hunt provides an API, eliminating the need to parse the page. However, the request limit is quite restrictive. One potential solution is to store all team access tokens in an array, but this approach is not confirmed yet.

@Sddilora Sddilora self-assigned this May 21, 2024
@Sddilora
Copy link
Member Author

Sddilora commented May 21, 2024

currently working script ( get if the given users voted for the given product with access token)

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

const ACCESS_TOKEN = "" // fill with an access token

type User struct {
	Username string `json:"username"`
	UserID   string `json:"userID"`
}

func main() {
	productSlug := "multi-ai-chat" // This is just an example product slug, but this product really exists.

	// Read users from JSON file
	users, err := readUsersFromFile("users.json")
	if err != nil {
		fmt.Println("Error reading users:", err)
		return
	}

	// Get product ID by slug
	productID, err := getProductIDBySlug(productSlug)
	if err != nil {
		fmt.Println("Error getting product ID:", err)
		return
	}

	// Check if users voted for the product
	for _, user := range users {
		voted, err := hasUserVoted(productID, user.UserID)
		if err != nil {
			fmt.Printf("Error checking if user %s voted: %v\n", user.Username, err)
			continue
		}

		if voted {
			fmt.Printf("User %s voted for the product.\n", user.Username)
		} else {
			fmt.Printf("User %s did not vote for the product.\n", user.Username)
		}
	}
}

func readUsersFromFile(filename string) ([]User, error) {
	var users []User

	file, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(file, &users)
	if err != nil {
		return nil, err
	}

	return users, nil
}

func getProductIDBySlug(slug string) (string, error) {
	query := map[string]string{
		"query": fmt.Sprintf(`query { post(slug: "%s") { id } }`, slug),
	}

	queryJSON, err := json.Marshal(query)
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest("POST", "https://api.producthunt.com/v2/api/graphql", bytes.NewBuffer(queryJSON))
	if err != nil {
		return "", err
	}

	req.Header.Set("Authorization", "Bearer "+ACCESS_TOKEN)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var jsonResponse map[string]interface{}
	if err := json.Unmarshal(body, &jsonResponse); err != nil {
		return "", err
	}

	data, ok := jsonResponse["data"].(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("unexpected response format: missing data field")
	}

	post, ok := data["post"].(map[string]interface{})
	if !ok {
		return "", fmt.Errorf("unexpected response format: missing post field")
	}

	productID, ok := post["id"].(string)
	if !ok {
		return "", fmt.Errorf("unexpected response format: missing product ID")
	}

	return productID, nil
}

func hasUserVoted(productID string, userID string) (bool, error) {
	var cursor *string

	for {
		// Prepare the cursor clause if a cursor is available
		cursorClause := ""
		if cursor != nil {
			cursorClause = fmt.Sprintf(`after: "%s", `, *cursor)
		}

		query := map[string]string{
			"query": fmt.Sprintf(`query {
				post(id: "%s") {
					votes(%sfirst: 20) {
						nodes {
							userId
						}
						pageInfo {
							endCursor
							hasNextPage
						}
					}
				}
			}`, productID, cursorClause),
		}

		queryJSON, err := json.Marshal(query)
		if err != nil {
			return false, err
		}

		req, err := http.NewRequest("POST", "https://api.producthunt.com/v2/api/graphql", bytes.NewBuffer(queryJSON))
		if err != nil {
			return false, err
		}

		req.Header.Set("Authorization", "Bearer "+ACCESS_TOKEN)
		req.Header.Set("Accept", "application/json")
		req.Header.Set("Content-Type", "application/json")

		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			return false, err
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return false, err
		}

		var jsonResponse map[string]interface{}
		if err := json.Unmarshal(body, &jsonResponse); err != nil {
			return false, err
		}

		data, ok := jsonResponse["data"].(map[string]interface{})
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing data field")
		}

		post, ok := data["post"].(map[string]interface{})
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing post field")
		}

		votes, ok := post["votes"].(map[string]interface{})
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing votes field")
		}

		nodes, ok := votes["nodes"].([]interface{})
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing nodes field")
		}

		for _, node := range nodes {
			if node.(map[string]interface{})["userId"] == userID {
				return true, nil
			}
		}

		pageInfo, ok := votes["pageInfo"].(map[string]interface{})
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing pageInfo field")
		}

		hasNextPage, ok := pageInfo["hasNextPage"].(bool)
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing hasNextPage field")
		}

		if !hasNextPage {
			break
		}

		endCursor, ok := pageInfo["endCursor"].(string)
		if !ok {
			return false, fmt.Errorf("unexpected response format: missing endCursor field")
		}
		cursor = &endCursor
	}

	return false, nil
}

users.json

[
  {
      "userID": "7099447",
      "username": "Semanur Guclu"
  },
  {
      "userID": "6562454",
      "username": "Dilara Doğan"
  },
  {
      "userID": "9876542",
      "username": "example_user2"
  },
  {
        "userID": "7109434",
        "username": "Ebrar Kesici"
  }
]

@Sddilora
Copy link
Member Author

Here is an example result:

User Semanur Guclu did not vote for the product.
User Dilara Doğan voted for the product.
User example_user2 did not vote for the product.
User Ebrar Kesici voted for the product.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Be Be Team research Investigation or exploration of new concepts, techniques, or approaches web scrap Web scraping is required
Projects
None yet
Development

No branches or pull requests

2 participants