graw

repository·master·Indexed 43 days ago

https://github.com/turnage/graw

A Go library for building Reddit bots using an event-driven model. It abstracts Reddit's event streams, allowing developers to implement handlers for new posts, comments, mentions, and private messages. Includes the act CLI for performing Reddit actions and the feed CLI for announcing Reddit feeds to standard output. As of version 1.0.0, the API is stable with guaranteed backwards compatibility.

Tokens
4.7K
Snippets
17
Records
24
Agent score
51%

What's inside graw

  1. How graw bots and event handling work

    master

    The core design of graw follows an event-driven model:

    1. Reddit acts as the source of events.
    2. graw connects the source to your logic.
    3. Your bot acts as a handler for those events.

    To create a bot, you define a struct and implement specific methods (handlers) that correspond to the events you want to process. For example, to handle new posts, you implement a Post method that accepts a *reddit.Post pointer. You then register this handler with an API handle from the github.com/turnage/graw/reddit package and specify which events to subscribe to.

    type announcer struct {}
    
    func (a *announcer) Post(post *reddit.Post) error {
            fmt.Printf("%s posted \"%s\"\n", post.Author, post.Title)
            return nil
    }
  2. Understand the graw API Promise

    master

    As of version 1.0.0, the graw API is stable and guarantees no backwards incompatible changes.

    There are only two specific scenarios where a change might break your code:

    1. Interface updates: New methods may be added to an interface. This only breaks your code if you embed the interface and have a method with the same name.
    2. Config struct updates: New fields may be added to the Config struct. This only breaks your code if you embed the struct and have a field with the same name, or if you initialize the struct positionally.
  3. Required handler interfaces for Scan

    master

    The handler passed to Scan must implement one or more of the following interfaces from the botfaces package, depending on the Config provided:

    • botfaces.PostHandler: Required if Config.Subreddits or Config.CustomFeeds are non-empty. Requires implementing the Post(p post) error method.
    • botfaces.CommentHandler: Required if Config.SubredditComments is non-empty. Requires implementing the Comment(c comment) error method.
    • botfaces.UserHandler: Required if Config.Users is non-empty. Requires implementing UserPost(p post) error and UserComment(c comment) error methods.
  4. Configure a graw run using the Config struct

    master

    The Config struct is used to specify the event sources that graw will monitor. Each event type (posts, comments, mentions, etc.) is routed to a specific handler defined in graw/botfaces.

    Key Configuration Fields:

    • Subreddits: A list of subreddit names to monitor for new posts. These are sent to PostHandler.
    • SubredditComments: A list of subreddit names to monitor for new comments. These are sent to CommentHandler.
    • CustomFeeds: A map where the key is a username and the value is a list of their custom feeds. New posts in these feeds are sent to PostHandler.
    • Users: A list of usernames to monitor for all new posts and comments. These are sent to UserHandler. Note: Monitoring many users can delay updates because a separate monitor must be constructed for every user.
    • PostReplies: If true, replies to posts made by the bot's account are sent to PostReplyHandler.
    • CommentReplies: If true, replies to comments made by the bot's account are sent to CommentReplyHandler.
    • Mentions: If true, mentions of the bot's username are sent to MentionHandler.
    • Messages: If true, messages sent to the bot's inbox are sent to MessageHandler.
    • Logger: An optional *log.Logger to capture internal debug messages.
    import (
    	"log"
    	"github.com/turnage/turnage/graw"
    )
    
    cfg := graw.Config{
    	Subreddits:       []string{"golang", "programming"},
    	SubredditComments: []string{"golang"},
    	PostReplies:      true,
    	Mentions:         true,
    	Logger:           log.Default(),
    }
  5. Available event streams in graw

    master

    graw provides robust event streams that can be processed by implementing corresponding handler methods. Supported streams include:

    • New posts in subreddits
    • New comments in subreddits
    • New posts or comments by users
    • Private messages sent to the bot
    • Replies to the bot's posts
    • Replies to the bot's comments
    • Mentions of the bot's username
  6. Troubleshoot Reddit authentication errors

    master

    When calling validateAuth(), you may encounter the following errors if credentials are incomplete:

    • missing oauth credentials: Occurs when ID or Secret is an empty string.
    • missing username: Occurs when Password is provided but Username is empty.
    • missing password: Occurs when Username is provided but Password is empty.
  7. Initialize a Reddit client with newClient()

    master

    Use newClient(c clientConfig) to create a new Reddit client. The client handles OAuth2 authorization automatically.

    Depending on the configuration of the App field in clientConfig, the client will either behave as an unauthenticated client (using only the provided agent User-Agent) or as an authenticated client using Reddit app credentials.

    To create an unauthenticated client, ensure c.app.unauthenticated() returns true. To create an authenticated client, provide a valid App configuration and call newClient.

    // Example conceptual usage
    client, err := newClient(clientConfig{
        agent: "MyGrawBot/1.0 by /u/username",
        app:   myAppConfig,
    })
    if err != nil {
        // handle error
    }
  8. Validate Reddit App credentials

    master

    Use the validateAuth() method on an App instance to ensure that the provided credentials are complete and valid for authentication.

    Validation rules:

    1. If ID or Secret are empty, it returns errMissingOauthCredentials.
    2. If Password is provided but Username is empty, it returns errMissingUsername.
    3. If Username is provided but Password is empty, it returns errMissingPassword.
    err := app.validateAuth()
    if err != nil {
        // handle error
    }
  9. Run the graw event loop with Run()

    master

    The Run function is the primary entry point for starting a graw bot. It connects a provided handler to the event streams requested in the Config.

    Run launches the execution in a background goroutine. It returns three values:

    1. A stop() function: Call this to gracefully terminate the graw run.
    2. A wait() function: This blocks until the graw run encounters an error.
    3. An error: Returned immediately if the initial stream connection fails.

    Important: Handler Requirements Depending on the flags set in your Config, your handler must implement specific interfaces from the botfaces package. If a requested stream is enabled in the config but the handler does not implement the corresponding interface, Run will return an error.

    // Example usage of Run
    stop, wait, err := graw.Run(myHandler, myRedditBot, graw.Config{
        PostReplies: true,
        Mentions:    true,
        // ... other config
    })
    
    if err != nil {
        log.Fatal(err)
    }
    
    // In a separate goroutine or main thread:
    // stop() // to terminate
    // err := wait() // to block until failure
  10. Start a Reddit event scan with Scan()

    master

    The Scan function initiates a background process to monitor Reddit events (posts, comments, or user activity) based on the provided Config. It requires a handler that implements specific interfaces from the botfaces package depending on what you are scanning for.

    Scan returns three values:

    1. A stop() function: Call this to immediately terminate the scan.
    2. A wait() function: This blocks until the scan encounters an error and returns that error.
    3. An error: Returned immediately if the initial setup or connection fails.

    Important Constraints:

    • If your Config includes PostReplies, CommentReplies, Mentions, or Messages, Scan will return a loggedOutErr. These specific event types require a logged-in bot session, whereas Scan is designed for logged-out event sources.
    • The handler must satisfy the required interfaces for the types of feeds requested in your Config (see interface requirements below).
    // Example usage of Scan
    stop, wait, err := graw.Scan(myHandler, myScript, myConfig)
    if err != nil {
    	// Handle immediate error
    }
    
    // In another goroutine or later
    stop()
    
    // Block until scan fails
    if err := wait(); err != nil {
    	// Handle scan failure
    }
  11. Execute requests with the client Do() method

    master

    The client interface provides a Do(*http.Request) ([]byte, error) method. This method executes the provided HTTP request and handles OAuth2 authorization invisibly.

    If the request is successful (HTTP 200 OK), it returns the response body as a byte slice. If the request fails, it returns an error. The method maps specific HTTP status codes to internal error types.

    // Assuming 'client' is an instance of the reddit client
    req, _ := http.NewRequest("GET", "https://www.reddit.com/r/all.json", nil)
    body, err := client.Do(req)
    if err != nil {
        // handle error
    }
    // use body ([]byte)