mira

package module
v9.0.0+incompatible Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2021 License: Apache-2.0 Imports: 14 Imported by: 0

README

Fork of MIRA

For the original project by @thecsw, please see https://github.com/thecsw/mira/.

Why

While I liked the base of the original library, especially mira/models was full of duplicate code. In this fork, I tried to tailor it down and have a more "go" approach at handling the different types of JSON data returned from Reddit, while minimizing duplicated code.

Because of the project I need the library for, I also added some mod relevant stuff that I haven't found in any other Go Reddit API Wrapper so far - mainly mod functions.

How

Please read the documentation over at https://pkg.go.dev/github.com/ttgmpsn/mira. It has some examples to get you started.

Documentation

Overview

Package mira or Meme Investor Reddit API

Fork by ttgmpsn. For the original project by @thecsw, please see https://github.com/thecsw/mira/.

Type Prefixes

Reddit IDs are in the form of PREFIX_ID, with ID being in base36. A post ID for example looks like t3_aaaaa. This is the current table of prefixes:

| Prefix | Type                             |
|--------|----------------------------------|
|   t1   | Comment                          |
|   t2   | Redditor                         |
|   t3   | Submission, PostListing contents |
|   t4   | Message                          |
|   t5   | Subreddit                        |
|   t6   | Award                            |

Index

Examples

Constants

View Source
const (
	RedditBase  = "https://www.reddit.com/"
	RedditOauth = "https://oauth.reddit.com"
)

RedditBase is the basic reddit URL, RedditOauth is the base URL for use once authenticated

Variables

This section is empty.

Functions

This section is empty.

Types

type Credentials

type Credentials struct {
	ClientID     string
	ClientSecret string
	Username     string
	Password     string
	UserAgent    string
	RedirectURL  string
}

Credentials stores information for authing towards the Reddit API. You can create an app here: https://old.reddit.com/prefs/apps/

type Interface

type Interface interface {
	GetID() string
	GetParentID() string
	GetTitle() string
	GetBody() string
	GetAuthor() string
	GetName() string
	GetKarma() float64
	GetUps() float64
	GetDowns() float64
	GetSubreddit() string
	GetCreated() float64
	GetFlair() string
	GetURL() string
	IsRoot() bool
}

Interface can be used for any reddit object (Post, Comment, etc.) to avoid type hinting.

type NotifyRefreshTokenSource

type NotifyRefreshTokenSource struct {
	// contains filtered or unexported fields
}

NotifyRefreshTokenSource is essentially oauth2.ResuseTokenSource with TokenNotifyFunc added.

func (*NotifyRefreshTokenSource) Token

func (s *NotifyRefreshTokenSource) Token() (*oauth2.Token, error)

Token returns the current token if it's still valid, else will refresh the current token (using r.Context for HTTP client information) and return the new one.

type Reddit

type Reddit struct {
	Client *http.Client

	OAuthConfig *oauth2.Config
	TokenExpiry time.Time
	UserAgent   string

	Chain  chan *chainVals
	Values redditVals
	// contains filtered or unexported fields
}

Reddit holds the connection to the API for a user. You can have multiple Reddit instances at the same time (see Example below).

Calling Methods

Each method is called similarly: First, you queue an object (Subreddit, Comment, Redditor etc) you want to perform the action on. Afterwards (this can be done in the same call), you select the command:

reddit.Subreddit("iama").Submit("I just did a bot, AMA", "Hey all! I just created a bot. AMA.")
reddit.Redditor("spez").Compose("Hi spez!", "Hi spez how are you?")

Working with submissions

If you call a function that returns a post or comment, you will receive an object that confirms to the models.Submission interface. You should be able to get most of the values without type hinting with the functions provided in the interface:

submission = reddit.SubmissionInfoID("t3_aaaaa")
fmt.Println("Got a post with body:", post.GetBody())

If you need detailed information, you can do a type assertion:

post, _ := submission.(*miramodels.Post)
fmt.Println("Post Flair Text:", post.LinkFlairText)
Example

If you have multiple users in parallel, you can initialize multiple reddit objects to switch between them:

package main

import (
	"github.com/ttgmpsn/mira"
)

func main() {
	redditInstances := make(map[string]*mira.Reddit)
	// Login as users - heavily shortened, see CodeAuth / LoginAuth examples:
	// User 1: ttgmpsn
	reddit1 := mira.Init(mira.Credentials{})
	reddit1.CodeAuth("", nil)
	redditInstances["ttgmpsn"] = reddit1

	// User 2: ttgmbot
	reddit2 := mira.Init(mira.Credentials{})
	reddit2.CodeAuth("", nil)
	redditInstances["ttgmbot"] = reddit2

	// If redditInstances is a global variable, you can now use it everywhere!
}
Output:

func Init

func Init(c Credentials) *Reddit

Init will initialize the Reddit instance. Note that you most likely want to auth using LoginAuth() or CodeAuth() afterwards, see the examples there.

func (*Reddit) Approve

func (c *Reddit) Approve() error

Approve the last queued object. Valid objects: Comment, Post

func (*Reddit) AuthCodeURL

func (c *Reddit) AuthCodeURL(state string, scopes []string) string

AuthCodeURL creates and returns an auth URL which contains an auth code.

func (*Reddit) Ban

func (c *Reddit) Ban(redditor string, days int, context, message, reason string) error

Ban bans a redditor from last queued object. Valid objects: Subreddit

func (*Reddit) CodeAuth

func (c *Reddit) CodeAuth(code string, f TokenNotifyFunc) error

CodeAuth creates and sets a token using an authentication code returned from AuthCodeURL. Note that Username & Password provided to Init are ignored. You can optionally pass a TokenNotifyFunc to get notified when the token changes (i.e. to store it into a database). Pass nil if you do not want to use this.

Example
package main

import (
	"fmt"

	"github.com/ttgmpsn/mira"
	miramodels "github.com/ttgmpsn/mira/models"
)

func main() {
	reddit := mira.Init(mira.Credentials{
		ClientID:     "clientid",
		ClientSecret: "clientsecret",
		UserAgent:    "MIRA CodeAuth Example v0",
		RedirectURL:  "https://example.com/auth", // This must be equal to the value you set in the reddit app config, or you will get an error!
	})

	// Provide all scopes you need to OAuthConfig
	reddit.OAuthConfig.Scopes = []string{"identity", "submit"}

	// Step 1: Generate URL to redirect user to:
	// The state is a unique value you can use to distinguish between user requests. It won't be used by reddit, just returned to your app later (see below).
	state := "UniqueStateToDistinguish"
	// AuthURL is a link to reddit.com, where a user has to confirm the scopes you request, and can choose to accept/decline the auth request.
	AuthURL := reddit.AuthCodeURL(state, reddit.OAuthConfig.Scopes)
	fmt.Printf("Visit this URL to continue the Authentication Flow: %s\n", AuthURL)

	// Step 2: If a user approves your app, he will be redirected to your RedirectURL.
	// In this example, the request would look like https://example.com/auth?state=UniqueStateToDistinguish&code=TOPSECRETCODE
	// With the code, you can then continue with Step 2 of the authentication flow: Actually getting your access token.
	// The code below usually is in a separate function that handles the "auth" endpoint. Just re-initialize "reddit" like above.
	code := "TOPSECRETCODE" // usually would be sth. like r.URL.Query()["code"]
	if err := reddit.CodeAuth(code, nil); err != nil {
		panic(err)
	}

	// Done! You can now use reddit authenticated as that user & can use reddit:
	rMeObj, err := reddit.Me().Info()
	if err != nil {
		panic(err)
	}
	rMe, _ := rMeObj.(*miramodels.Me)
	fmt.Printf("You are now logged in, /u/%s\n", rMe.Name)

	// Note that the access token cannot be retreived separately. This is by design, since it is useless after a few hours.
	// Instead, you should use a TokenNotifyFunc. See example there.
}
Output:

Example (ResumingSession)

Assumung you have saved the Refresh Token (see example for TokenNotifyFunc), this is how you can restore a session using it:

package main

import (
	"fmt"
	"time"

	"github.com/ttgmpsn/mira"
	miramodels "github.com/ttgmpsn/mira/models"
	"golang.org/x/oauth2"
)

func main() {
	handleRefreshToken := func(t *oauth2.Token) error {
		// See TokenNotifyFunc example
		return nil
	}
	reddit := mira.Init(mira.Credentials{
		ClientID:     "clientid",
		ClientSecret: "clientsecret",
		UserAgent:    "MIRA CodeAuth Example v0",
		RedirectURL:  "https://example.com/auth", // This must be equal to the value you set in the reddit app config, or you will get an error!
	})
	reddit.OAuthConfig.Scopes = []string{"identity", "submit"}

	refreshToken := "secret" // fetched from database or similar

	reddit.SetToken(&oauth2.Token{
		AccessToken:  "VOID",
		RefreshToken: refreshToken,
		Expiry:       time.Now().Add(time.Minute * -1), // Set the Access Token to expire immediately so a new one is fetched
	}, reddit.OAuthConfig.Scopes, handleRefreshToken)

	// Done! You can now use reddit authenticated as that user & can use reddit:
	rMeObj, err := reddit.Me().Info()
	if err != nil {
		panic(err)
	}
	rMe, _ := rMeObj.(*miramodels.Me)
	fmt.Printf("You are now logged in again, /u/%s\n", rMe.Name)
}
Output:

func (*Reddit) Comment

func (c *Reddit) Comment(name string) *Reddit

Comment queues up the next action to be about a certain comment.

func (*Reddit) Comments

func (c *Reddit) Comments(sort string, tdur string, limit int) ([]*models.Comment, error)

Comments gets comments for the last queued object. Valid objects: Subreddit, Post, Redditor

func (*Reddit) CommentsAfter

func (c *Reddit) CommentsAfter(sort string, last models.RedditID, limit int) ([]*models.Comment, error)

CommentsAfter gets comments for the last queued object after a given item. Valid objects: Subreddit, Redditor

func (*Reddit) Compose

func (c *Reddit) Compose(subject, text string) error

Compose writes a private message to the last queued object. Valid objects: Redditor

func (*Reddit) Delete

func (c *Reddit) Delete() error

Delete the last queued object. Valid objects: Comment, Post

func (*Reddit) Distinguish

func (c *Reddit) Distinguish(how string, sticky bool) error

Distinguish the last queued object. Valid objects: Comment

func (*Reddit) Edit

func (c *Reddit) Edit(text string) (*models.Comment, error)

Edit the last queued object. Valid objects: Comment, Post

func (*Reddit) EditWiki added in v0.1.11

func (c *Reddit) EditWiki(page, content, reason string) error

EditWiki edits/creates a wiki page from last queued object. Valid objects: Subreddit

func (*Reddit) GetModMailByID added in v0.1.1

func (c *Reddit) GetModMailByID(conversationID string, markRead bool) (*models.NewModmailConversation, error)

GetModMailByID returns the ModMail Conversation for a given modmail ID

func (*Reddit) GetParentPost

func (c *Reddit) GetParentPost() (models.RedditID, error)

GetParentPost returns the Post ID for the last queued object. Valid objects: Comment

func (*Reddit) Info

func (c *Reddit) Info() (models.RedditThing, error)

Info returns general information about the queued object as a mira.Interface.

func (*Reddit) ListUnreadMessages

func (c *Reddit) ListUnreadMessages() ([]*models.Comment, error)

ListUnreadMessages for the last queued object. Valid objects: Me

func (*Reddit) LoginAuth

func (c *Reddit) LoginAuth() error

LoginAuth creates the required HTTP client with a new token. Creds are taken from the data provided to Init. Tokens are refreshed automatically shortly before the session runs out.

Example
package main

import (
	"fmt"

	"github.com/ttgmpsn/mira"
	miramodels "github.com/ttgmpsn/mira/models"
)

func main() {
	reddit := mira.Init(mira.Credentials{
		ClientID:     "clientid",
		ClientSecret: "clientsecret",
		Username:     "reddit_username",
		Password:     "topsecretpassword",
		UserAgent:    "MIRA LoginAuth Example v0",
	})

	err := reddit.LoginAuth()
	if err != nil {
		panic(err)
	}

	// You can now use reddit as an authenticated user:
	rMeObj, err := reddit.Me().Info()
	if err != nil {
		panic(err)
	}
	rMe, _ := rMeObj.(*miramodels.Me)
	fmt.Printf("You are now logged in, /u/%s\n", rMe.Name)
}
Output:

func (*Reddit) Me

func (c *Reddit) Me() *Reddit

Me queues up the next action to be about the logged in user.

func (*Reddit) MiraRequest

func (c *Reddit) MiraRequest(method string, target string, payload map[string]string) ([]byte, error)

MiraRequest can be used to make custom requests to the reddit API.

func (*Reddit) ModLog

func (c *Reddit) ModLog(limit int, mod string) ([]*models.ModAction, error)

ModLog returns the mod log from the last queued object. Valid objects: Subreddit

func (*Reddit) ModQueue

func (c *Reddit) ModQueue(limit int) ([]models.Submission, error)

ModQueue returns the mod queue from the last queued object. Valid objects: Subreddit

func (*Reddit) Post

func (c *Reddit) Post(name string) *Reddit

Post queues up the next action to be about a certain Post.

func (*Reddit) Posts

func (c *Reddit) Posts(sort string, tdur string, limit int) ([]*models.Post, error)

Posts gets posts for the last queued object. Valid objects: Subreddit, Redditor

func (*Reddit) PostsAfter

func (c *Reddit) PostsAfter(last models.RedditID, limit int) ([]*models.Post, error)

PostsAfter gets posts for the last queued object after a given item. Valid objects: Subreddit, Redditor

func (*Reddit) ReadAllMessages

func (c *Reddit) ReadAllMessages() error

ReadAllMessages marks all message for the last queued object as read. Valid objects: Me

func (*Reddit) ReadMessage

func (c *Reddit) ReadMessage(messageID string) error

ReadMessage marks a message for the last queued object as read. Valid objects: Me

func (*Reddit) Redditor

func (c *Reddit) Redditor(name string) *Reddit

Redditor queues up the next action to be about a certain Redditor.

func (*Reddit) Remove

func (c *Reddit) Remove(spam bool) error

Remove mod-removes the last queued object. To remove own comments, please use Delete() Valid objects: Comment, Post

func (*Reddit) Reply

func (c *Reddit) Reply(text string) (*models.CommentActionResponse, error)

Reply adds a comment to the last queued object. Valid objects: Comment, Post

func (*Reddit) ReplyWithID

func (c *Reddit) ReplyWithID(name, text string) (*models.CommentActionResponse, error)

ReplyWithID adds a comment to the given thing id, without it needing to be queued up.

func (*Reddit) SelectFlair

func (c *Reddit) SelectFlair(text string) error

SelectFlair for the last queued object. Valid objects: Post

func (*Reddit) SetDefault

func (c *Reddit) SetDefault()

SetDefault gets sensible default values for streams.

func (*Reddit) SetToken

func (c *Reddit) SetToken(t *oauth2.Token, scopes []string, f TokenNotifyFunc) error

SetToken manually assigns a token to the Reddit object. This is useful if you have your token information saved from a prior run. You can optionally pass a TokenNotifyFunc to get notified when the token changes (i.e. to store it into a database). Pass nil if you do not want to use this.

func (*Reddit) StreamComments added in v0.1.5

func (c *Reddit) StreamComments() (*SubmissionStream, error)

StreamComments streams comments for the last queued object. Valid objects: Subreddit, (Redditor)

func (*Reddit) StreamPosts added in v0.1.5

func (c *Reddit) StreamPosts() (*SubmissionStream, error)

StreamPosts streams posts for the last queued object. Valid objects: Subreddit, (Redditor)

Example
package main

import (
	"fmt"

	"github.com/ttgmpsn/mira"
	miramodels "github.com/ttgmpsn/mira/models"
)

func main() {
	// Initialize reddit instance like usually - see other examples.
	reddit := mira.Init(mira.Credentials{})

	// Create stream
	stream, err := reddit.Subreddit("pics").StreamPosts()
	if err != nil {
		panic(err)
	}

	// Create listener
	go func() {
		var s miramodels.Submission
		for s = range stream.C {
			if s == nil {
				fmt.Println("Stream was closed")
				return
			}
			fmt.Println("Received new item in stream:", s.GetID())
		}
	}()
}
Output:

func (*Reddit) Stylesheet added in v0.1.10

func (c *Reddit) Stylesheet() (*models.Stylesheet, error)

Stylesheet returns the stylesheet & images from last queued object. Valid objects: Subreddit

func (*Reddit) SubmissionInfo

func (c *Reddit) SubmissionInfo() (models.Submission, error)

SubmissionInfo returns general information about the queued submission.

func (*Reddit) SubmissionInfoID

func (c *Reddit) SubmissionInfoID(name models.RedditID) (models.Submission, error)

SubmissionInfoID returns general information about the submission ID.

func (*Reddit) Submissions added in v0.1.7

func (c *Reddit) Submissions(limit int) ([]models.Submission, error)

Submissions gets submissions for the last queued object. Valid objects: Redditor

func (*Reddit) SubmissionsAfter added in v0.1.7

func (c *Reddit) SubmissionsAfter(last models.RedditID, limit int) ([]models.Submission, error)

SubmissionsAfter gets submissions for the last queued object after a given item. Valid objects: Redditor

func (*Reddit) Submit

func (c *Reddit) Submit(title string, text string) (*models.PostActionResponse, error)

Submit submits a new Post to the last queued object. Valid objects: Subreddit

func (*Reddit) Subreddit

func (c *Reddit) Subreddit(name ...string) *Reddit

Subreddit queues up the next action to be about one or multuple Subreddits.

func (*Reddit) UpdateSidebar

func (c *Reddit) UpdateSidebar(text string) error

UpdateSidebar of the last queued object. Valid objects: Subreddit

func (*Reddit) UserFlair

func (c *Reddit) UserFlair(user, text string) error

UserFlair assigns a specific flair to a user on the last queued object. Valid objects: Subreddit

func (*Reddit) Wiki

func (c *Reddit) Wiki(page string) (*models.Wiki, error)

Wiki returns a wiki page from last queued object. Valid objects: Subreddit

type RedditErr

type RedditErr struct {
	Message string `json:"message"`
	Error   string `json:"error"`
}

RedditErr is an error returned from the Reddit API.

type SubmissionStream added in v0.1.5

type SubmissionStream struct {
	C     <-chan models.Submission
	Close chan struct{}
}

SubmissionStream has two objects: a channel "C" where you can receive Submissions (posts or comments), and a channel "close" - close that channel to stop receiving events. Please use the close channel appropriately or you'll be polling reddit non-stop!

type TokenNotifyFunc

type TokenNotifyFunc func(*oauth2.Token) error

TokenNotifyFunc is a function that accepts an oauth2 Token upon refresh, and returns an error if it should not be used. Use this to cache Refresh Token if you want to (you'll most likely want to). Taken from https://github.com/golang/oauth2/issues/84#issuecomment-332517319

Example

This example assumes you have read and understand the CodeAuth() example.

To save the refresh token & grab a new access token once it expires, you can use a notify function that will be called each time the token is refreshed (which happens automatically while your app is running)

package main

import (
	"fmt"

	"github.com/ttgmpsn/mira"
	"golang.org/x/oauth2"
)

func main() {
	reddit := mira.Init(mira.Credentials{
		ClientID:     "clientid",
		ClientSecret: "clientsecret",
		UserAgent:    "MIRA TokenNotifyFunc Example v0",
		RedirectURL:  "https://example.com/auth",
	})
	reddit.OAuthConfig.Scopes = []string{"identity", "submit"}

	// handleRefreshToken statifies the mira.TokenNotifyFunc interface
	handleRefreshToken := func(t *oauth2.Token) error {
		fmt.Println("Refreshed Token:")
		fmt.Println("- New Access Token:", t.AccessToken)
		fmt.Println("- New Refresh Token:", t.RefreshToken)

		// Probably save to Database or similar.
		return nil
	}

	// Step 1 is similar to the CodeAuth example. See there.
	// Step 2 changes slightly. For extended documentation please see the CodeAuth example.
	code := "TOPSECRETCODE" // usually would be sth. like r.URL.Query()["code"]
	if err := reddit.CodeAuth(code, handleRefreshToken); err != nil {
		panic(err)
	}

	// This is it! Each time the token will be refreshed (which happens automatically), handleRefreshToken() will be called with the latest information.
	// If you have multiple users, you should probably add something to distinguish between them :)
}
Output:

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL