Skip to content

Commit

Permalink
Added basic patreon integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jogramming committed Aug 3, 2018
1 parent 802b5c6 commit 1309878
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
7 changes: 7 additions & 0 deletions cmd/yagpdb/sampleenvfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,10 @@ export YAGPDB_ANNOUNCEMENTS_CHANNEL=""

# Datadog integration
export YAGPDB_DOGSTATSDADDRESS="127.0.0.1:8125"

# Pass a api key to list pledges on your patreon on the website
# This refresh token will only work and will only be used once, the api returns a new token which we store in redis
# under the patreon_refresh_token key, therefor if you wipe redis you will have to update this refresh token again
export YAGPDB_PATREON_API_REFRESH_TOKEN=""
export YAGPDB_PATREON_API_CLIENT_ID=""
export YAGPDB_PATREON_API_CLIENT_SECRET=""
20 changes: 20 additions & 0 deletions cmd/yagpdb/templates/cp_selectserver.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@ <h2 class="card-title">Information</h2>
<p>If you stumble into any issue and need help then join the support server. (link at the top of the page)</p>
</div>
</section>
{{if .patreonActive}}
<section class="card">
<header class="card-header">
<h2 class="card-title">Thanks to my patrons!</h2>
</header>
<div class="card-body">
<b>Quality patrons</b><br>
{{range .qualityPatrons}}
<img class="avatar" src="{{.Attributes.ImageURL}}" data-toggle="tooltip" data-placement="top" title="" data-original-title="{{or .Attributes.Vanity .Attributes.FirstName}}"></img>
{{end}}

<br>

<b>Patrons</b><br>
{{range .normalPatrons}}
<img class="avatar" src="{{.Attributes.ImageURL}}" data-toggle="tooltip" data-placement="top" title="" data-original-title="{{or .Attributes.Vanity .Attributes.FirstName}}"></img>
{{end}}
</div>
</section>
{{end}}
<section class="card">
<header class="card-header">
<h2 class="card-title">Check out...</h2>
Expand Down
10 changes: 10 additions & 0 deletions web/handlers_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/jonas747/yagpdb/bot/botrest"
"github.com/jonas747/yagpdb/common"
"github.com/jonas747/yagpdb/web/discordblog"
"github.com/jonas747/yagpdb/web/patreon"
"github.com/mediocregopher/radix.v3"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -39,6 +40,15 @@ func HandleSelectServer(w http.ResponseWriter, r *http.Request) interface{} {
}
}

if patreon.ActivePoller != nil {
normalPatrons, qualityPatrons := patreon.ActivePoller.GetPatrons()
if len(normalPatrons) > 0 || len(qualityPatrons) > 0 {
tmpl["patreonActive"] = true
tmpl["normalPatrons"] = normalPatrons
tmpl["qualityPatrons"] = qualityPatrons
}
}

posts := discordblog.GetNewestPosts(10)
tmpl["Posts"] = posts

Expand Down
202 changes: 202 additions & 0 deletions web/patreon/patreon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package patreon

import (
"context"
"github.com/jonas747/yagpdb/common"
"github.com/mediocregopher/radix.v3"
"github.com/mxpv/patreon-go"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"os"
"sync"
"time"
)

var ActivePoller *Poller

type Poller struct {
mu sync.RWMutex
config *oauth2.Config
token *oauth2.Token
client *patreon.Client

normalPatrons []*patreon.User
qualityPatrons []*patreon.User
}

func Run() {

accessToken := os.Getenv("YAGPDB_PATREON_API_ACCESS_TOKEN")
refreshToken := os.Getenv("YAGPDB_PATREON_API_REFRESH_TOKEN")
clientID := os.Getenv("YAGPDB_PATREON_API_CLIENT_ID")
clientSecret := os.Getenv("YAGPDB_PATREON_API_CLIENT_SECRET")

if accessToken == "" || clientID == "" || clientSecret == "" {
logrus.Warn("Patreon: Missing one of YAGPDB_PATREON_API_ACCESS_TOKEN, YAGPDB_PATREON_API_CLIENT_ID, YAGPDB_PATREON_API_CLIENT_SECRET, not starting patreon integration.")
return
}

var storedRefreshToken string
common.RedisPool.Do(radix.Cmd(&storedRefreshToken, "GET", "patreon_refresh_token"))

config := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: patreon.AuthorizationURL,
TokenURL: patreon.AccessTokenURL,
},
Scopes: []string{"users", "pledges-to-me", "my-campaign"},
}

token := &oauth2.Token{
AccessToken: "",
RefreshToken: refreshToken,
// Must be non-nil, otherwise token will not be expired
Expiry: time.Now().Add(-24 * time.Hour),
}

tc := oauth2.NewClient(context.Background(), &TokenSourceSaver{inner: config.TokenSource(context.Background(), token)})

pClient := patreon.NewClient(tc)
user, err := pClient.FetchUser()
if err != nil {
if storedRefreshToken == "" {
logrus.WithError(err).Error("Patreon: Failed fetching current user with env var refresh token, no refresh token stored in redis, not starting patreon integration")
return
}

logrus.WithError(err).Warn("Patreon: Failed fetching current user with env var refresh token, trying stored token")
tCop := *token
tCop.RefreshToken = storedRefreshToken

tc = oauth2.NewClient(context.Background(), &TokenSourceSaver{inner: config.TokenSource(context.Background(), &tCop)})
pClient = patreon.NewClient(tc)

user, err = pClient.FetchUser()
if err != nil {
logrus.WithError(err).Error("Patreon: Failed fetching current user with stored token, not starting patreon integration")
return
}
}

poller := &Poller{
config: config,
token: token,
client: pClient,
}

ActivePoller = poller

logrus.Info("Patreon integration activated as ", user.Data.ID, ": ", user.Data.Attributes.FullName)
go poller.Run()
}

func (p *Poller) Run() {
ticker := time.NewTicker(time.Minute)
for {
p.Poll()
<-ticker.C
}
}

func (p *Poller) Poll() {
// Get your campaign data
campaignResponse, err := p.client.FetchCampaign()
if err != nil {
logrus.WithError(err).Error("Patreon: Failed fetching campaign")
return
}

campaignId := campaignResponse.Data[0].ID

cursor := ""
page := 1

normalPatrons := make([]*patreon.User, 0, 25)
qualityPatrons := make([]*patreon.User, 0, 25)
for {
pledgesResponse, err := p.client.FetchPledges(campaignId,
patreon.WithPageSize(25),
patreon.WithCursor(cursor))

if err != nil {
logrus.WithError(err).Error("Patreon: Failed fetching pledges")
return
}

// Get all the users in an easy-to-lookup way
users := make(map[string]*patreon.User)
for _, item := range pledgesResponse.Included.Items {
u, ok := item.(*patreon.User)
if !ok {
continue
}

users[u.ID] = u
}

// Loop over the pledges to get e.g. their amount and user name
for _, pledge := range pledgesResponse.Data {
if !pledge.Attributes.DeclinedSince.Time.IsZero() {
continue
}

amount := pledge.Attributes.AmountCents
user, ok := users[pledge.Relationships.Patron.Data.ID]
if !ok {
continue
}

if amount <= 200 {
normalPatrons = append(normalPatrons, user)
} else {
qualityPatrons = append(qualityPatrons, user)
}

// fmt.Printf("%s is pledging %d cents\r\n", patronFullName, amount)
}

// Get the link to the next page of pledges
nextLink := pledgesResponse.Links.Next
if nextLink == "" {
break
}

cursor = nextLink
page++
}

// Swap the stored ones, this dosent mutate the existing returned slices so we dont have to do any copying on each request woo
p.mu.Lock()
p.normalPatrons = normalPatrons
p.qualityPatrons = qualityPatrons
p.mu.Unlock()
}

func (p *Poller) GetPatrons() (normal []*patreon.User, quality []*patreon.User) {
p.mu.RLock()
normal = p.normalPatrons
quality = p.qualityPatrons
p.mu.RUnlock()

return
}

type TokenSourceSaver struct {
inner oauth2.TokenSource
lastRefreshToken string
}

func (t *TokenSourceSaver) Token() (*oauth2.Token, error) {
tk, err := t.inner.Token()
if err == nil {
if t.lastRefreshToken != tk.RefreshToken {
logrus.Info("Patreon: New refresh token")
common.RedisPool.Do(radix.Cmd(nil, "SET", "patreon_refresh_token", tk.RefreshToken))
t.lastRefreshToken = tk.RefreshToken
}
}

return tk, err
}
3 changes: 3 additions & 0 deletions web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/jonas747/yagpdb/common"
yagtmpl "github.com/jonas747/yagpdb/common/templates"
"github.com/jonas747/yagpdb/web/discordblog"
"github.com/jonas747/yagpdb/web/patreon"
"github.com/natefinch/lumberjack"
log "github.com/sirupsen/logrus"
"goji.io"
Expand Down Expand Up @@ -114,6 +115,8 @@ func Run() {
go discordblog.RunPoller(common.BotSession, parsedBlogChannel, time.Minute)
}

patreon.Run()

LoadAd()

log.Info("Running webservers")
Expand Down

0 comments on commit 1309878

Please sign in to comment.