refactor of controllers for scrobbling
This commit is contained in:
parent
d4043ebc88
commit
3b7d937df5
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
||||||
"git.jamesravey.me/ravenscroftj/indiescrobble/scrobble"
|
"git.jamesravey.me/ravenscroftj/indiescrobble/services/scrobble"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,27 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
||||||
"git.jamesravey.me/ravenscroftj/indiescrobble/scrobble"
|
"git.jamesravey.me/ravenscroftj/indiescrobble/services/scrobble"
|
||||||
"git.jamesravey.me/ravenscroftj/indiescrobble/services/micropub"
|
"git.jamesravey.me/ravenscroftj/indiescrobble/services/micropub"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func Scrobble(c *gin.Context){
|
type ScrobbleController struct{
|
||||||
|
db *gorm.DB
|
||||||
|
scrobbler *scrobble.Scrobbler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScrobbleController(db *gorm.DB) *ScrobbleController{
|
||||||
|
return &ScrobbleController{db, scrobble.NewScrobbler(db)}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Do the actual post to the user's site*/
|
||||||
|
func (s *ScrobbleController) DoScrobble(c *gin.Context){
|
||||||
|
|
||||||
err := c.Request.ParseForm()
|
err := c.Request.ParseForm()
|
||||||
|
|
||||||
|
@ -21,22 +33,56 @@ func Scrobble(c *gin.Context){
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
||||||
"message": err,
|
"message": err,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add validation of type
|
s.scrobbler.Scrobble(&c.Request.Form, currentUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*Display the scrobble form and allow user to search for and add media*/
|
||||||
|
func (s *ScrobbleController) ScrobbleForm(c *gin.Context){
|
||||||
|
|
||||||
|
err := c.Request.ParseForm()
|
||||||
|
|
||||||
|
// this is an authed endpoint so 'user' must be set and if not panicking is fair
|
||||||
|
currentUser := c.MustGet("user").(*models.BaseUser)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
||||||
|
"message": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
scrobbleType := c.Request.Form.Get("type")
|
scrobbleType := c.Request.Form.Get("type")
|
||||||
|
|
||||||
searchEngine := scrobble.NewSearchProvider(scrobbleType)
|
if c.Request.Form.Get("item") != "" {
|
||||||
|
|
||||||
var searchResults []scrobble.ScrobbleMetaRecord = nil
|
item, err := s.scrobbler.GetItemByID(&c.Request.Form)
|
||||||
var item scrobble.ScrobbleMetaRecord = nil
|
|
||||||
|
|
||||||
query := c.Request.Form.Get("q")
|
|
||||||
itemID := c.Request.Form.Get("item")
|
|
||||||
|
|
||||||
if itemID != "" {
|
|
||||||
|
|
||||||
item, err = searchEngine.SearchProvider.GetItem(itemID)
|
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
||||||
|
"message": err,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}else{
|
||||||
|
c.HTML(http.StatusOK, "scrobble.tmpl", gin.H{
|
||||||
|
"user": currentUser,
|
||||||
|
"scrobbleType": scrobbleType,
|
||||||
|
"scrobblePlaceholder": scrobble.ScrobblePlaceholders[scrobbleType],
|
||||||
|
"scrobbleTypeName": scrobble.ScrobbleTypeNames[scrobbleType],
|
||||||
|
"item": item,
|
||||||
|
"now": time.Now().Format("2006-01-02T15:04"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}else if query := c.Request.Form.Get("q"); query != "" {
|
||||||
|
|
||||||
|
searchResults, err := s.scrobbler.Search(&c.Request.Form)
|
||||||
|
|
||||||
if err != nil{
|
if err != nil{
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
||||||
|
@ -44,32 +90,29 @@ func Scrobble(c *gin.Context){
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}else if query != "" {
|
|
||||||
var err error = nil
|
|
||||||
searchResults, err = searchEngine.SearchProvider.Search(query)
|
|
||||||
|
|
||||||
if err != nil{
|
c.HTML(http.StatusOK, "scrobble.tmpl", gin.H{
|
||||||
c.HTML(http.StatusBadRequest, "error.tmpl", gin.H{
|
"user": currentUser,
|
||||||
"message": err,
|
"scrobbleType": scrobbleType,
|
||||||
})
|
"scrobblePlaceholder": scrobble.ScrobblePlaceholders[scrobbleType],
|
||||||
return
|
"scrobbleTypeName": scrobble.ScrobbleTypeNames[scrobbleType],
|
||||||
}
|
"searchEngine": s.scrobbler.GetSearchEngineNameForType(scrobbleType),
|
||||||
|
"searchResults": searchResults,
|
||||||
|
"now": time.Now().Format("2006-01-02T15:04"),
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
c.HTML(http.StatusOK, "scrobble.tmpl", gin.H{
|
||||||
|
"user": currentUser,
|
||||||
|
"scrobbleType": scrobbleType,
|
||||||
|
"scrobblePlaceholder": scrobble.ScrobblePlaceholders[scrobbleType],
|
||||||
|
"scrobbleTypeName": scrobble.ScrobbleTypeNames[scrobbleType],
|
||||||
|
"now": time.Now().Format("2006-01-02T15:04"),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "scrobble.tmpl", gin.H{
|
|
||||||
"user": currentUser,
|
|
||||||
"scrobbleType": scrobbleType,
|
|
||||||
"scrobblePlaceholder": scrobble.ScrobblePlaceholders[scrobbleType],
|
|
||||||
"scrobbleTypeName": scrobble.ScrobbleTypeNames[scrobbleType],
|
|
||||||
"searchEngine": searchEngine.SearchProvider.GetName(),
|
|
||||||
"searchResults": searchResults,
|
|
||||||
"item": item,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PreviewScrobble(c *gin.Context){
|
func (s *ScrobbleController) PreviewScrobble(c *gin.Context){
|
||||||
|
|
||||||
err := c.Request.ParseForm()
|
err := c.Request.ParseForm()
|
||||||
|
|
||||||
|
@ -84,7 +127,7 @@ func PreviewScrobble(c *gin.Context){
|
||||||
|
|
||||||
scrobbleType := c.Request.Form.Get("type")
|
scrobbleType := c.Request.Form.Get("type")
|
||||||
|
|
||||||
searchEngine := scrobble.NewSearchProvider(scrobbleType)
|
searchEngine := scrobble.NewSearchProvider(scrobbleType, s.db)
|
||||||
|
|
||||||
itemID := c.Request.Form.Get("item")
|
itemID := c.Request.Form.Get("item")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MediaItem struct{
|
||||||
|
gorm.Model
|
||||||
|
MediaID string `gorm:"not null uniqueIndex"`
|
||||||
|
ThumbnailURL sql.NullString
|
||||||
|
DisplayName sql.NullString
|
||||||
|
CanonicalURL sql.NullString
|
||||||
|
Data sql.NullString
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
gorm.Model
|
||||||
|
URL string `gorm:"uniqueIndex"`
|
||||||
|
PostType string `gorm:"index"`
|
||||||
|
UserID int `gorm:"foreignKey"`
|
||||||
|
User User
|
||||||
|
MediaItemID int `gorm:"index"`
|
||||||
|
MediaItem MediaItem
|
||||||
|
ScrobbledAt sql.NullTime
|
||||||
|
Content sql.NullString
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var alphanum = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
||||||
|
|
||||||
|
func randSeq(n int) string {
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = alphanum[rand.Intn(len(alphanum))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseUser struct {
|
||||||
|
Me string
|
||||||
|
Token string
|
||||||
|
UserRecord *User
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
Me string `gorm:"uniqueIndex"`
|
||||||
|
APIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GenerateRandomKey() {
|
||||||
|
u.APIKey = randSeq(16)
|
||||||
|
}
|
|
@ -1,76 +0,0 @@
|
||||||
package scrobble
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/StalkR/imdb"
|
|
||||||
"github.com/gregjones/httpcache"
|
|
||||||
"github.com/gregjones/httpcache/diskcache"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IMDBMetaRecord struct{
|
|
||||||
title imdb.Title
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IMDBMetaRecord) GetID() string{
|
|
||||||
return r.title.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IMDBMetaRecord) GetDisplayName() string{
|
|
||||||
return fmt.Sprintf("%v (%v)", r.title.Name, r.title.Year)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (r *IMDBMetaRecord) GetCanonicalURL() string{
|
|
||||||
return r.title.URL
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *IMDBMetaRecord) GetThumbnailURL() string{
|
|
||||||
return r.title.Poster.ContentURL
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type IMDBScrobbleMetadataProvider struct {
|
|
||||||
client *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIMDBProvider() *IMDBScrobbleMetadataProvider {
|
|
||||||
|
|
||||||
cache := diskcache.New("cache")
|
|
||||||
client := &http.Client{Transport: httpcache.NewTransport(cache)}
|
|
||||||
return &IMDBScrobbleMetadataProvider{client:client}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (i *IMDBScrobbleMetadataProvider) GetName() string { return "IMDB" }
|
|
||||||
|
|
||||||
|
|
||||||
func (i *IMDBScrobbleMetadataProvider) GetItem(id string) (ScrobbleMetaRecord, error) {
|
|
||||||
|
|
||||||
title, err := imdb.NewTitle(i.client, id)
|
|
||||||
|
|
||||||
if err != nil{
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &IMDBMetaRecord{title: *title}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IMDBScrobbleMetadataProvider) Search(query string) ([]ScrobbleMetaRecord, error) {
|
|
||||||
|
|
||||||
titles, err := imdb.SearchTitle(i.client, query)
|
|
||||||
|
|
||||||
if err != nil{
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
records := make([]ScrobbleMetaRecord, len(titles))
|
|
||||||
|
|
||||||
for i, title := range titles {
|
|
||||||
records[i] = &IMDBMetaRecord{title: title}
|
|
||||||
}
|
|
||||||
|
|
||||||
return records, nil
|
|
||||||
}
|
|
|
@ -36,8 +36,11 @@ func NewRouter(db *gorm.DB) *gin.Engine {
|
||||||
authed := router.Use(middlewares.AuthMiddleware(true, iam))
|
authed := router.Use(middlewares.AuthMiddleware(true, iam))
|
||||||
|
|
||||||
// add scrobble endpoints
|
// add scrobble endpoints
|
||||||
authed.GET("/scrobble", controllers.Scrobble)
|
scrobbleController := controllers.NewScrobbleController(db)
|
||||||
authed.POST("/scrobble/preview", controllers.PreviewScrobble)
|
|
||||||
|
authed.GET("/scrobble", scrobbleController.ScrobbleForm)
|
||||||
|
|
||||||
|
authed.POST("/scrobble/preview", scrobbleController.PreviewScrobble)
|
||||||
|
|
||||||
// v1 := router.Group("v1")
|
// v1 := router.Group("v1")
|
||||||
// {
|
// {
|
||||||
|
|
|
@ -30,6 +30,7 @@ func Init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
db.AutoMigrate(&models.User{})
|
db.AutoMigrate(&models.User{})
|
||||||
|
db.AutoMigrate(&models.Post{})
|
||||||
|
|
||||||
r := NewRouter(db)
|
r := NewRouter(db)
|
||||||
r.LoadHTMLGlob("templates/*.tmpl")
|
r.LoadHTMLGlob("templates/*.tmpl")
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package micropub
|
||||||
|
|
||||||
|
type MicroPubPostType struct{
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MicroPubSyndicateTarget struct{
|
||||||
|
Name string
|
||||||
|
Uid string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MicroPubConfig struct{
|
||||||
|
MediaEndpoint string `json:"media-endpoint"`
|
||||||
|
PostTypes []MicroPubPostType `json:"post-types"`
|
||||||
|
SyndicateTargets []MicroPubSyndicateTarget `json:"syndicate-to"`
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package micropub
|
||||||
|
|
||||||
|
type HEntry struct{
|
||||||
|
Type []string
|
||||||
|
Properties map[string]interface{}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
package micropub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
const(USER_AGENT_STRING="IndieScrobble (indiescrobble.club)")
|
||||||
|
|
||||||
|
type MicropubDiscoveryService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MicropubDiscoveryService) doGet(url string ) (*http.Response, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
|
||||||
|
req.Header.Add("User-Agent", USER_AGENT_STRING)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (m *MicropubDiscoveryService) doAuthGet(url string, bearerToken string ) (*http.Response, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
|
||||||
|
req.Header.Add("User-Agent", USER_AGENT_STRING)
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v",bearerToken))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MicropubDiscoveryService) findMicropubEndpoint(me string) (string, error) {
|
||||||
|
|
||||||
|
res, err := m.doGet(me)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the HTML document
|
||||||
|
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the review items
|
||||||
|
s := doc.Find("link[rel=micropub]")
|
||||||
|
|
||||||
|
if s.Length() < 1 {
|
||||||
|
return "", fmt.Errorf("no micropub endpoint found for %v", me)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the returned URL
|
||||||
|
endpointUrl, err := url.Parse(s.AttrOr("href",""))
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !endpointUrl.IsAbs(){
|
||||||
|
|
||||||
|
if(strings.HasPrefix(endpointUrl.Path, "/")) {
|
||||||
|
|
||||||
|
newUrl := *res.Request.URL
|
||||||
|
newUrl.Path = endpointUrl.Path
|
||||||
|
|
||||||
|
return newUrl.String(), nil
|
||||||
|
}else{
|
||||||
|
return res.Request.URL.String() + endpointUrl.Path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
return endpointUrl.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MicropubDiscoveryService) getMicropubConfig(endpoint string, authToken string) (*MicroPubConfig, error) {
|
||||||
|
|
||||||
|
configUrl, err := url.Parse(endpoint)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
q := configUrl.Query()
|
||||||
|
q.Set("q","config")
|
||||||
|
configUrl.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
res, err := m.doAuthGet(configUrl.String(), authToken)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config := MicroPubConfig{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, &config)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discover endpoints for given me/domain identifier */
|
||||||
|
func (m *MicropubDiscoveryService) Discover(me string, authToken string) (*MicroPubConfig, error) {
|
||||||
|
|
||||||
|
endpoint, err := m.findMicropubEndpoint(me)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("Failed to get endpoint: %v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get endpoint config
|
||||||
|
config, err := m.getMicropubConfig(endpoint, authToken)
|
||||||
|
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
log.Printf("Failed to get configuration: %v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send micropub to endpoint */
|
||||||
|
func (m *MicropubDiscoveryService) SubmitScrobble(currentUser *models.BaseUser, post *models.Post) (error) {
|
||||||
|
endpoint, err := m.findMicropubEndpoint(currentUser.Me)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
postObj := make(map[string]interface{})
|
||||||
|
postObj["type"] = []string{"h-entry"}
|
||||||
|
postObj["visibility"] = []string{"public"}
|
||||||
|
|
||||||
|
properties := make(map[string]interface{})
|
||||||
|
properties["media-type"] = []string{post.PostType}
|
||||||
|
properties["media-item-id"] = []string{post.MediaItem.MediaID}
|
||||||
|
properties["media-item-url"] = []string{post.MediaItem.CanonicalURL.String}
|
||||||
|
|
||||||
|
properties["indiescrobble-id"] = post.MediaItem.ID
|
||||||
|
|
||||||
|
if post.MediaItem.ThumbnailURL.Valid{
|
||||||
|
properties["photo"] = []string{post.MediaItem.ThumbnailURL.String}
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Content.Valid{
|
||||||
|
postObj["content"] = post.Content.String
|
||||||
|
}
|
||||||
|
|
||||||
|
postObj["properties"] = properties
|
||||||
|
|
||||||
|
bodyBytes, err := json.Marshal(postObj)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := bytes.NewReader(bodyBytes)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", endpoint, body)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("User-Agent", USER_AGENT_STRING)
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", currentUser.Token))
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, err := resp.Location()
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
post.URL = loc.String()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,136 @@
|
||||||
|
package scrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
||||||
|
"github.com/StalkR/imdb"
|
||||||
|
"github.com/gregjones/httpcache"
|
||||||
|
"github.com/gregjones/httpcache/diskcache"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IMDBMetaRecord struct{
|
||||||
|
title imdb.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IMDBMetaRecord) GetID() string{
|
||||||
|
return r.title.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IMDBMetaRecord) GetDisplayName() string{
|
||||||
|
return fmt.Sprintf("%v (%v)", r.title.Name, r.title.Year)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (r *IMDBMetaRecord) GetCanonicalURL() string{
|
||||||
|
return r.title.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IMDBMetaRecord) GetThumbnailURL() string{
|
||||||
|
return r.title.Poster.ContentURL
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type IMDBScrobbleMetadataProvider struct {
|
||||||
|
client *http.Client
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIMDBProvider(db *gorm.DB) *IMDBScrobbleMetadataProvider {
|
||||||
|
|
||||||
|
cache := diskcache.New("cache")
|
||||||
|
client := &http.Client{Transport: httpcache.NewTransport(cache)}
|
||||||
|
return &IMDBScrobbleMetadataProvider{client:client, db:db}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (i *IMDBScrobbleMetadataProvider) GetName() string { return "IMDB" }
|
||||||
|
|
||||||
|
|
||||||
|
func titleFromMediaItem(mediaItem *models.MediaItem) imdb.Title {
|
||||||
|
title := imdb.Title{ID: mediaItem.MediaID, }
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
func imdbRecordFromMediaItem(mediaItem *models.MediaItem) IMDBMetaRecord {
|
||||||
|
title := imdb.Title{}
|
||||||
|
json.Unmarshal([]byte(mediaItem.Data.String), &title)
|
||||||
|
return IMDBMetaRecord{title:title}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imdbRecordToMediaItem(record *IMDBMetaRecord) (*models.MediaItem, error){
|
||||||
|
|
||||||
|
marshalledTitle, err := json.Marshal(record.title)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
item := models.MediaItem{
|
||||||
|
MediaID: record.title.ID,
|
||||||
|
ThumbnailURL: sql.NullString{String: record.GetThumbnailURL(), Valid:true},
|
||||||
|
CanonicalURL: sql.NullString{String: record.GetCanonicalURL(), Valid: true},
|
||||||
|
DisplayName: sql.NullString{String: record.GetDisplayName(), Valid: true},
|
||||||
|
Data: sql.NullString{String: string(marshalledTitle), Valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &item, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (i *IMDBScrobbleMetadataProvider) GetItem(id string) (ScrobbleMetaRecord, error) {
|
||||||
|
|
||||||
|
// see if item is in db first
|
||||||
|
item := models.MediaItem{}
|
||||||
|
|
||||||
|
result := i.db.Where(&models.MediaItem{MediaID: id}).First(&item)
|
||||||
|
|
||||||
|
if result.Error == nil{
|
||||||
|
record := imdbRecordFromMediaItem(&item)
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
title, err := imdb.NewTitle(i.client, id)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache the title in db and store
|
||||||
|
record := IMDBMetaRecord{title: *title}
|
||||||
|
mediaItem, err := imdbRecordToMediaItem(&record)
|
||||||
|
|
||||||
|
result = i.db.Create(mediaItem)
|
||||||
|
|
||||||
|
if result.Error != nil{
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &record, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IMDBScrobbleMetadataProvider) Search(query string) ([]ScrobbleMetaRecord, error) {
|
||||||
|
|
||||||
|
titles, err := imdb.SearchTitle(i.client, query)
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
records := make([]ScrobbleMetaRecord, len(titles))
|
||||||
|
|
||||||
|
for i, title := range titles {
|
||||||
|
records[i] = &IMDBMetaRecord{title: title}
|
||||||
|
}
|
||||||
|
|
||||||
|
return records, nil
|
||||||
|
}
|
|
@ -1,23 +1,20 @@
|
||||||
package scrobble
|
package scrobble
|
||||||
|
|
||||||
|
import "gorm.io/gorm"
|
||||||
|
|
||||||
|
|
||||||
type MetaSearchProvider struct{
|
type MetaSearchProvider struct{
|
||||||
ScrobbleType string
|
ScrobbleType string
|
||||||
SearchProvider ScrobbleMetaProvider
|
SearchProvider ScrobbleMetaProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSearchProvider(scrobbleType string) *MetaSearchProvider{
|
func NewSearchProvider(scrobbleType string, db *gorm.DB) *MetaSearchProvider{
|
||||||
provider := &MetaSearchProvider{ScrobbleType: scrobbleType}
|
provider := &MetaSearchProvider{ScrobbleType: scrobbleType}
|
||||||
|
|
||||||
if scrobbleType == SCROBBLE_TYPE_MOVIE {
|
if scrobbleType == SCROBBLE_TYPE_MOVIE {
|
||||||
provider.SearchProvider = NewIMDBProvider()
|
provider.SearchProvider = NewIMDBProvider(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (m *MetaSearchProvider) search(query string) {
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package scrobble
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"git.jamesravey.me/ravenscroftj/indiescrobble/models"
|
||||||
|
"git.jamesravey.me/ravenscroftj/indiescrobble/services/micropub"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scrobbler struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScrobbler(db *gorm.DB) *Scrobbler{
|
||||||
|
return &Scrobbler{db:db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrobbler) ValidateType(form *url.Values) error {
|
||||||
|
|
||||||
|
scrobbleType := form.Get("type")
|
||||||
|
if _, ok := ScrobbleTypeNames[scrobbleType]; !ok{
|
||||||
|
return fmt.Errorf("Unknown/invalid scrobble type %v", scrobbleType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrobbler) GetItemByID(form *url.Values) (ScrobbleMetaRecord, error) {
|
||||||
|
|
||||||
|
if err := s.ValidateType(form); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchEngine := NewSearchProvider(form.Get("type"), s.db)
|
||||||
|
|
||||||
|
item, err := searchEngine.SearchProvider.GetItem(form.Get("item"))
|
||||||
|
|
||||||
|
if err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrobbler) Search(form *url.Values) ([]ScrobbleMetaRecord, error) {
|
||||||
|
|
||||||
|
if err := s.ValidateType(form); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
searchEngine := NewSearchProvider(form.Get("type"), s.db)
|
||||||
|
|
||||||
|
query := form.Get("q")
|
||||||
|
|
||||||
|
return searchEngine.SearchProvider.Search(query)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrobbler) GetSearchEngineNameForType(scrobbleType string) string {
|
||||||
|
return NewSearchProvider(scrobbleType, s.db).SearchProvider.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Scrobbler) Scrobble(form *url.Values, currentUser *models.BaseUser) (*models.Post, error) {
|
||||||
|
|
||||||
|
if err := s.ValidateType(form); err != nil{
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
item := models.MediaItem{}
|
||||||
|
result := s.db.Where(&models.MediaItem{MediaID: form.Get("item")}).First(&item)
|
||||||
|
|
||||||
|
if result.Error != nil{
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
discovery := micropub.MicropubDiscoveryService{}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
mediaItem := models.MediaItem{}
|
||||||
|
|
||||||
|
post := models.Post{MediaItem: mediaItem, User: *currentUser.UserRecord, PostType: form.Get("type") }
|
||||||
|
|
||||||
|
discovery.SubmitScrobble(currentUser, &post)
|
||||||
|
|
||||||
|
return &post, nil
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<form method="POST" action="/scrobble/preview">
|
<form method="POST" action="/scrobble/preview">
|
||||||
|
|
||||||
<p><a href="/">Add A Post</a> > <a href="/scrobble?type=movie">Add {{ .scrobbleTypeName }}</a> > {{.item.GetDisplayName}}</p>
|
<p><a href="/">Add A Post</a> > <a href="/scrobble?type={{ .scrobbleType }}">Add {{ .scrobbleTypeName }}</a> > {{.item.GetDisplayName}}</p>
|
||||||
|
|
||||||
<h3>Preview Post: {{.item.GetDisplayName}}</h3>
|
<h3>Preview Post: {{.item.GetDisplayName}}</h3>
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="type" value="{{ .scrobbleType }}"/>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<label>When: </label> <input type="datetime-local" name='when'/><br/>
|
<label>When: </label> <input type="datetime-local" name='when' value="{{.now}}"/><br/>
|
||||||
|
|
||||||
<label>Rating: (out of 5)</label> <input type="number" name='rating'/><br/>
|
<label>Rating: (out of 5)</label> <input type="number" name='rating'/><br/>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue