add patches

This commit is contained in:
Michael Zhang 2020-10-14 15:58:01 -05:00
parent 301379574a
commit 32b87975d0
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 271 additions and 125 deletions

View file

@ -4,8 +4,9 @@ subscribe-bot
Subscribes to OSU map updates and stores versions for later review.
Please don't run a separate bot, the official one is `subscribe-bot#8789`. If
you want to contribute or test the bot, then here are instructions on how to
run it:
you want to contribute or test the bot, instructions on how to run it are below.
Join the [Discord][2]
How to run
----------
@ -37,3 +38,4 @@ License
[GPL3][1]
[1]: https://www.gnu.org/licenses/gpl-3.0.en.html
[2]: https://discord.gg/eqjVG2H

1
go.mod
View file

@ -6,6 +6,7 @@ require (
github.com/BurntSushi/toml v0.3.1
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bwmarrin/discordgo v0.22.0
github.com/dustin/go-humanize v1.0.0
github.com/foolin/goview v0.3.0
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
github.com/gin-gonic/contrib v0.0.0-20201005132743-ca038bbf2944

2
go.sum
View file

@ -15,6 +15,8 @@ github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CL
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=

67
web/auth.go Normal file
View file

@ -0,0 +1,67 @@
package web
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
func (web *Web) logout(c *gin.Context) {
session := sessions.Default(c)
session.Delete("access_token")
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/")
}
func (web *Web) login(c *gin.Context) {
url := url.URL{
Scheme: "https",
Host: "osu.ppy.sh",
Path: "/oauth/authorize",
}
q := url.Query()
q.Set("client_id", web.config.Oauth.ClientId)
q.Set("redirect_uri", web.config.Web.ServedAt+"/login/callback")
q.Set("response_type", "code")
q.Set("scope", "identify public")
q.Set("state", "urmom")
url.RawQuery = q.Encode()
fmt.Println("redirecting to", url.String())
c.Redirect(http.StatusTemporaryRedirect, url.String())
}
func (web *Web) loginCallback(c *gin.Context) {
receivedCode := c.Query("code")
bodyQuery := url.Values{}
bodyQuery.Set("client_id", web.config.Oauth.ClientId)
bodyQuery.Set("client_secret", web.config.Oauth.ClientSecret)
bodyQuery.Set("code", receivedCode)
bodyQuery.Set("grant_type", "authorization_code")
bodyQuery.Set("redirect_uri", web.config.Web.ServedAt+"/login/callback")
body := strings.NewReader(bodyQuery.Encode())
resp, _ := web.hc.Post("https://osu.ppy.sh/oauth/token", "application/x-www-form-urlencoded", body)
respBody, _ := ioutil.ReadAll(resp.Body)
type OsuToken struct {
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var token OsuToken
_ = json.Unmarshal(respBody, &token)
fmt.Println("TOKEN", token)
session := sessions.Default(c)
session.Set("access_token", token.AccessToken)
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/")
}

73
web/repo.go Normal file
View file

@ -0,0 +1,73 @@
package web
import (
"errors"
"io"
"net/http"
"path"
"time"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
func (web *Web) mapVersions(c *gin.Context) {
userId := c.Param("userId")
mapId := c.Param("mapId")
repoDir := path.Join(web.config.Repos, userId, mapId)
repo, _ := git.PlainOpen(repoDir)
type Revision struct {
Date time.Time
HumanDate string
Summary string
Hash string
HasParent bool
}
versions := make([]Revision, 0)
logIter, _ := repo.Log(&git.LogOptions{})
for {
commit, err := logIter.Next()
if err == io.EOF {
break
}
stats, _ := commit.Stats()
_, err = commit.Parent(0)
hasParent := !errors.Is(err, object.ErrParentNotFound)
versions = append(versions, Revision{
Date: commit.Author.When,
HumanDate: humanize.Time(commit.Author.When),
Summary: stats.String(),
Hash: commit.Hash.String(),
HasParent: hasParent,
})
}
c.HTML(http.StatusOK, "map-version.html", gin.H{
"LoggedIn": isLoggedIn(c),
"Versions": versions,
})
}
func (web *Web) mapPatch(c *gin.Context) {
userId := c.Param("userId")
mapId := c.Param("mapId")
hash := c.Param("hash")
repoDir := path.Join(web.config.Repos, userId, mapId)
repo, _ := git.PlainOpen(repoDir)
hashObj := plumbing.NewHash(hash)
commit, _ := repo.CommitObject(hashObj)
parent, _ := commit.Parent(0)
patch, _ := commit.Patch(parent)
c.String(http.StatusOK, "text/plain", patch.String())
}

View file

@ -1,51 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>subscribe-bot</title>
{{ define "content" }}
<meta name="viewport" content="width=device-width, initial-scale=1">
<p>Maps:</p>
<link rel="stylesheet" href="/static/main.css" />
</head>
<table class="table-auto">
<thead>
<th>Links</th>
<th>Title</th>
<th>Mapper</th>
</thead>
<tbody>
{{ range .Beatmapsets }}
<tr>
<td>
<a href="https://osu.ppy.sh/s/{{ .ID }}" target="_blank">osu</a>
<a href="/map/{{ .UserID }}/{{ .ID }}/versions">versions</a>
</td>
<td>{{ .Artist }} - {{ .Title }}</td>
<td>
<a href="https://osu.ppy.sh/u/{{ .UserID }}" target="_blank">{{ .Creator }}</a>
</td>
</tr>
{{ end }}
</tbody>
</table>
<body>
<div class="container">
<h1 class="title">
<a href="/">subscribe-bot</a>
</h1>
<small>logging in with your osu account does nothing</small>
<div class="nav-bar">
<a href="/">home</a>
{{ if .LoggedIn }}
<a href="/logout">logout</a>
{{ else }}
<a href="/login">login</a>
{{ end }}
</div>
<p>Maps:</p>
<table class="table-auto">
<thead>
<th>Mapper</th>
<th>Title</th>
<th>Links</th>
</thead>
{{ range .Beatmapsets }}
<tbody>
<td>
<a href="https://osu.ppy.sh/u/{{ .UserID }}" target="_blank">{{ .Creator }}</a>
</td>
<td>{{ .Artist }} - {{ .Title }}</td>
<td>
<a href="https://osu.ppy.sh/s/{{ .ID }}" target="_blank">osu</a>
</td>
</tbody>
{{ end }}
</table>
</div>
</body>
</html>
{{ end }}

View file

@ -0,0 +1,27 @@
{{ define "content" }}
<small>up to the latest 20 revisions, pagination coming later</small>
<table>
<thead>
<th>Date</th>
<th>Links</th>
<th>Summary</th>
</thead>
<tbody>
{{ range .Versions }}
<tr>
<td><span title="{{ .Date }}">{{ .HumanDate }}</span></td>
<td>
{{ if .HasParent }}
<a href="patch/{{ .Hash }}">patch</a>
{{ end }}
</td>
<td><pre>{{ .Summary }}</pre></td>
</tr>
{{ end }}
</tbody>
</table>
{{ end }}

32
web/templates/master.html Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>subscribe-bot</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/main.css" />
</head>
<body>
<div class="container">
<h1 class="title">
<a href="/">subscribe-bot</a>
</h1>
<small>logging in with your osu account does nothing</small>
<div class="nav-bar">
<a href="/">home</a>
{{ if .LoggedIn }}
<a href="/logout">logout</a>
{{ else }}
<a href="/login">login</a>
{{ end }}
</div>
{{ template "content" . }}
</div>
</body>
</html>

View file

@ -1,15 +1,12 @@
package web
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"sync"
"time"
@ -32,108 +29,77 @@ var (
cache = memoize.NewMemoizer(90*time.Second, 10*time.Minute)
)
type Web struct {
config *config.Config
api *osuapi.Osuapi
hc *http.Client
}
func RunWeb(config *config.Config, api *osuapi.Osuapi) {
hc := http.Client{
hc := &http.Client{
Timeout: 10 * time.Second,
}
if !config.Debug {
web := Web{config, api, hc}
web.Run()
}
func (web *Web) Run() {
if !web.config.Debug {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
r.Use(gin.Recovery())
r.Use(static.Serve("/static", static.LocalFile("web/static", false)))
r.Use(sessions.Sessions("mysession", sessions.NewCookieStore([]byte(config.Web.SessionSecret))))
r.Use(sessions.Sessions("mysession", sessions.NewCookieStore([]byte(web.config.Web.SessionSecret))))
r.HTMLRender = ginview.New(goview.Config{
Root: "web/templates",
DisableCache: config.Debug,
Master: "master.html",
DisableCache: web.config.Debug,
})
r.GET("/logout", func(c *gin.Context) {
session := sessions.Default(c)
session.Delete("access_token")
session.Save()
r.GET("/logout", web.logout)
c.Redirect(http.StatusTemporaryRedirect, "/")
})
r.GET("/login", web.login)
r.GET("/login", func(c *gin.Context) {
url := url.URL{
Scheme: "https",
Host: "osu.ppy.sh",
Path: "/oauth/authorize",
}
q := url.Query()
q.Set("client_id", config.Oauth.ClientId)
q.Set("redirect_uri", config.Web.ServedAt+"/login/callback")
q.Set("response_type", "code")
q.Set("scope", "identify public")
q.Set("state", "urmom")
url.RawQuery = q.Encode()
fmt.Println("redirecting to", url.String())
c.Redirect(http.StatusTemporaryRedirect, url.String())
})
r.GET("/login/callback", web.loginCallback)
r.GET("/login/callback", func(c *gin.Context) {
receivedCode := c.Query("code")
r.GET("/map/:userId/:mapId/versions", web.mapVersions)
bodyQuery := url.Values{}
bodyQuery.Set("client_id", config.Oauth.ClientId)
bodyQuery.Set("client_secret", config.Oauth.ClientSecret)
bodyQuery.Set("code", receivedCode)
bodyQuery.Set("grant_type", "authorization_code")
bodyQuery.Set("redirect_uri", config.Web.ServedAt+"/login/callback")
body := strings.NewReader(bodyQuery.Encode())
resp, _ := hc.Post("https://osu.ppy.sh/oauth/token", "application/x-www-form-urlencoded", body)
respBody, _ := ioutil.ReadAll(resp.Body)
type OsuToken struct {
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
var token OsuToken
_ = json.Unmarshal(respBody, &token)
fmt.Println("TOKEN", token)
session := sessions.Default(c)
session.Set("access_token", token.AccessToken)
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/")
})
r.GET("/map/:userId/:mapId/patch/:hash", web.mapPatch)
r.GET("/", func(c *gin.Context) {
session := sessions.Default(c)
var accessToken string
loggedIn := false
accessTokenI := session.Get("access_token")
if accessTokenI != nil {
accessToken = accessTokenI.(string)
if len(accessToken) > 0 {
loggedIn = true
}
}
beatmapSets := getRepos(config, api)
// render with master
beatmapSets := web.listRepos()
c.HTML(http.StatusOK, "index.html", gin.H{
"LoggedIn": loggedIn,
"LoggedIn": isLoggedIn(c),
"Beatmapsets": beatmapSets,
})
})
addr := fmt.Sprintf("%s:%d", config.Web.Host, config.Web.Port)
addr := fmt.Sprintf("%s:%d", web.config.Web.Host, web.config.Web.Port)
r.Run(addr)
}
func getRepos(config *config.Config, api *osuapi.Osuapi) []osuapi.Beatmapset {
func isLoggedIn(c *gin.Context) bool {
session := sessions.Default(c)
var accessToken string
loggedIn := false
accessTokenI := session.Get("access_token")
if accessTokenI != nil {
accessToken = accessTokenI.(string)
if len(accessToken) > 0 {
loggedIn = true
}
}
return loggedIn
}
func (web *Web) listRepos() []osuapi.Beatmapset {
expensive := func() (interface{}, error) {
repos := make([]int, 0)
reposDir := config.Repos
reposDir := web.config.Repos
users, _ := ioutil.ReadDir(reposDir)
for _, user := range users {
@ -155,7 +121,7 @@ func getRepos(config *config.Config, api *osuapi.Osuapi) []osuapi.Beatmapset {
for i, repo := range repos {
wg.Add(1)
go func(i int, repo int) {
bs, _ := api.GetBeatmapSet(repo)
bs, _ := web.api.GetBeatmapSet(repo)
beatmapSets[i] = bs
wg.Done()
}(i, repo)