poc for modifying notes, read and store collection, write template csv
template CSV to fill in can be created with `./main -template test.csv`, additional args `-print` and `-reload` print and reload the collection cache respectively. proof of concept for modifying notes available but not used yet. go-discogs library was originally a submodule, however is now a fork at github.com/hrfee/go-discogs.
This commit is contained in:
parent
2bae75cc3f
commit
b5d8eeb7a4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
personal_access_token
|
personal_access_token
|
||||||
|
test.csv
|
||||||
|
collection.gob
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "go-discogs"]
|
||||||
|
path = go-discogs
|
||||||
|
url = https://github.com/irlndts/go-discogs.git
|
2
go.mod
2
go.mod
@ -2,4 +2,4 @@ module git.hrfee.pw/hrfee/discogs-pricer
|
|||||||
|
|
||||||
go 1.22.4
|
go 1.22.4
|
||||||
|
|
||||||
require github.com/irlndts/go-discogs v0.3.6 // indirect
|
require github.com/hrfee/go-discogs v0.0.0-20240905170225-50ee0e6a98ae // indirect
|
||||||
|
6
go.sum
6
go.sum
@ -1,2 +1,4 @@
|
|||||||
github.com/irlndts/go-discogs v0.3.6 h1:3oIJEkLGQ1ffJcoo6wvtawPI4/SyHoRpnu25Y51U4wg=
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
github.com/irlndts/go-discogs v0.3.6/go.mod h1:UVQ05FdCzH4P/usnSxQDh77QYE37HvmPnSCgogioljo=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/hrfee/go-discogs v0.0.0-20240905170225-50ee0e6a98ae h1:9uX6ArLOVUzryMkT3VKud4c/5vNwWEwdR6vJHHvh2tw=
|
||||||
|
github.com/hrfee/go-discogs v0.0.0-20240905170225-50ee0e6a98ae/go.mod h1:Z34PS6moBg1QdybW9LkrqciRJHKdyQ9hpxvaiH8sFic=
|
||||||
|
271
main.go
271
main.go
@ -1,43 +1,290 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/irlndts/go-discogs"
|
"github.com/hrfee/go-discogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
CURRENCY = "GBP"
|
CURRENCY = "GBP"
|
||||||
USER_AGENT = "discogs-pricer/0.0 +https://git.hrfee.pw/hrfee/discogs-pricer"
|
USER_AGENT = "discogs-pricer/0.0 +https://git.hrfee.pw/hrfee/discogs-pricer"
|
||||||
|
|
||||||
|
STORE = "./collection.gob"
|
||||||
|
|
||||||
|
PER_PAGE = 75
|
||||||
|
|
||||||
|
NotesFieldID = 4 // Test, otherwise use 3
|
||||||
|
|
||||||
|
noPaToken = errors.New(`put "hrfee:<personal access token>" in ./personal_access_token and re-run.`)
|
||||||
)
|
)
|
||||||
|
|
||||||
/*type ServiceWriter interface {
|
type ItemHash string
|
||||||
WriteRow(svcID string, name string, price string, purchaseDate time.Time, notes string)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
c discogs.Discogs
|
Username string
|
||||||
|
c discogs.Discogs
|
||||||
|
StorePath string
|
||||||
|
CollectionCache struct {
|
||||||
|
Items map[ItemHash]CollectionItem // Hash of a load of IDs to Collection Item
|
||||||
|
Order []ItemHash
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(currency string, userAgent string, token string) (*Client, error) {
|
func NewClient(currency string, userAgent string, username, token string, storePath string) (*Client, error) {
|
||||||
client := Client{}
|
client := Client{Username: username, StorePath: storePath}
|
||||||
var err error
|
var err error
|
||||||
client.c, err = discogs.New(&discogs.Options{
|
client.c, err = discogs.New(&discogs.Options{
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
Currency: currency,
|
Currency: currency,
|
||||||
Token: token,
|
Token: token,
|
||||||
|
// URL: "http://localhost:8097",
|
||||||
})
|
})
|
||||||
|
if err == nil {
|
||||||
|
client.ClearCollectionCache()
|
||||||
|
err = client.ReadCollectionCache()
|
||||||
|
}
|
||||||
return &client, err
|
return &client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
// Items, error, and whether or not there's more pages to go.
|
||||||
token, err := os.ReadFile("personal_access_token")
|
func (c *Client) collection(pageNo int, allocate bool) ([]discogs.CollectionItemSource, error, bool) {
|
||||||
|
fmt.Printf("Getting collection page %d for user %s\n", pageNo, c.Username)
|
||||||
|
cf, err := c.c.CollectionItemsByFolder(c.Username, 0, &discogs.Pagination{
|
||||||
|
Sort: "added",
|
||||||
|
SortOrder: "asc",
|
||||||
|
Page: pageNo,
|
||||||
|
PerPage: PER_PAGE,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(errors.New("no token found in ./personal_access_token"))
|
return []discogs.CollectionItemSource{}, err, false
|
||||||
}
|
}
|
||||||
c, err := NewClient(CURRENCY, USER_AGENT, strings.TrimSuffix(string(token), "\n"))
|
if allocate {
|
||||||
fmt.Println("vim-go")
|
c.CollectionCache.Order = make([]ItemHash, 0, cf.Pagination.Pages*PER_PAGE)
|
||||||
|
}
|
||||||
|
return cf.Items, err, cf.Pagination.Page < cf.Pagination.Pages
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ClearCollectionCache() {
|
||||||
|
c.CollectionCache.Order = []ItemHash{}
|
||||||
|
c.CollectionCache.Items = map[ItemHash]CollectionItem{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PopulateCollectionCache() error {
|
||||||
|
more := true
|
||||||
|
var err error
|
||||||
|
var p []discogs.CollectionItemSource
|
||||||
|
i := 0
|
||||||
|
for more {
|
||||||
|
p, err, more = c.collection(i, i == 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range p {
|
||||||
|
ci := CollectionItem{p[i]}
|
||||||
|
hash := ci.Hash()
|
||||||
|
if _, ok := c.CollectionCache.Items[hash]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.CollectionCache.Order = append(c.CollectionCache.Order, hash)
|
||||||
|
c.CollectionCache.Items[hash] = ci
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return c.StoreCollectionCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) StoreCollectionCache() error {
|
||||||
|
file, err := os.Create(c.StorePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
enc := gob.NewEncoder(file)
|
||||||
|
return enc.Encode(c.CollectionCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReadCollectionCache() error {
|
||||||
|
file, err := os.Open(c.StorePath)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
dec := gob.NewDecoder(file)
|
||||||
|
return dec.Decode(&c.CollectionCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) CollectionJSON() (string, error) {
|
||||||
|
if c.CollectionCache.Order == nil || len(c.CollectionCache.Order) == 0 {
|
||||||
|
if err := c.PopulateCollectionCache(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := c.StoreCollectionCache(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _ := json.MarshalIndent(c.CollectionCache.Items, "", "\t")
|
||||||
|
return string(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WriteCSVTemplate(path string) error {
|
||||||
|
if c.CollectionCache.Order == nil || len(c.CollectionCache.Order) == 0 {
|
||||||
|
if err := c.PopulateCollectionCache(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.StoreCollectionCache(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
w := csv.NewWriter(file)
|
||||||
|
w.Write(HeaderRow)
|
||||||
|
for _, ciHash := range c.CollectionCache.Order {
|
||||||
|
w.Write(c.CollectionCache.Items[ciHash].RowFromCollectionItem())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TestEditField() {
|
||||||
|
err := c.c.EditFieldsInstance(c.Username, 1, 10191384, 313879623, discogs.NotesField, "a new 2nd value!")
|
||||||
|
fmt.Printf("Error: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var HeaderRow = strings.Split(
|
||||||
|
"Folder ID,Release ID,Instance ID,Artist - Name,Date Added,Price,Purchased Date,Additional Notes",
|
||||||
|
",",
|
||||||
|
)
|
||||||
|
|
||||||
|
type CollectionItem struct {
|
||||||
|
discogs.CollectionItemSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a real hash, but identifies a record uniquely enough.
|
||||||
|
func (ci CollectionItem) Hash() ItemHash {
|
||||||
|
return ItemHash(strconv.Itoa(ci.ID) + "|" + strconv.Itoa(ci.InstanceID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseTime(t string) (time.Time, error) {
|
||||||
|
return time.Parse("2006-01-02T15:04:05-07:00", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci CollectionItem) RawNotes() string {
|
||||||
|
notes := ""
|
||||||
|
for _, n := range ci.Notes {
|
||||||
|
if n.FieldID == discogs.NotesField {
|
||||||
|
notes = n.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notes
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParsedNotes struct {
|
||||||
|
Price string
|
||||||
|
PurchaseDate time.Time
|
||||||
|
Additional string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci CollectionItem) ParseNotes() ParsedNotes {
|
||||||
|
notes := ci.RawNotes()
|
||||||
|
pn := ParsedNotes{}
|
||||||
|
dateString := ""
|
||||||
|
n, err := fmt.Sscanf(notes, "Purchase Price: %s, Purchase Date: %s.%s", &(pn.Price), &dateString, &(pn.Additional))
|
||||||
|
if n <= 0 || err != nil {
|
||||||
|
pn.Additional = notes
|
||||||
|
}
|
||||||
|
pn.PurchaseDate, err = time.Parse("2006-01-02", dateString)
|
||||||
|
if err != nil || dateString == "" || pn.PurchaseDate.IsZero() {
|
||||||
|
pn.PurchaseDate, _ = ParseTime(ci.DateAdded)
|
||||||
|
}
|
||||||
|
return pn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci CollectionItem) RowFromCollectionItem() []string {
|
||||||
|
dateAdded, err := ParseTime(ci.DateAdded)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
notes := ci.ParseNotes()
|
||||||
|
row := []string{
|
||||||
|
strconv.Itoa(ci.FolderID),
|
||||||
|
strconv.Itoa(ci.ID),
|
||||||
|
strconv.Itoa(ci.InstanceID),
|
||||||
|
ci.BasicInformation.Artists[0].Name + " - " + ci.BasicInformation.Title,
|
||||||
|
dateAdded.Format("2006-01-02"),
|
||||||
|
notes.Price,
|
||||||
|
notes.PurchaseDate.Format("2006-01-02"),
|
||||||
|
notes.Additional,
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
patContent, err := os.ReadFile("personal_access_token")
|
||||||
|
if err != nil {
|
||||||
|
panic(noPaToken)
|
||||||
|
}
|
||||||
|
userAndToken := strings.Split(string(patContent), ":")
|
||||||
|
if len(userAndToken) != 2 {
|
||||||
|
panic(noPaToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := NewClient(CURRENCY, USER_AGENT, userAndToken[0], strings.TrimSuffix(string(userAndToken[1]), "\n"), STORE)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadCollection := false
|
||||||
|
printCollection := false
|
||||||
|
csvTemplatePath := ""
|
||||||
|
flag.BoolVar(&reloadCollection, "reload", false, "pass to force reload of collection cache.")
|
||||||
|
flag.BoolVar(&printCollection, "print", false, "pass to print JSON representation of collection.")
|
||||||
|
flag.StringVar(&csvTemplatePath, "template", "", "filepath to write template csv to.")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
args := false
|
||||||
|
flag.Visit(func(*flag.Flag) { args = true })
|
||||||
|
if !args {
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
if reloadCollection {
|
||||||
|
c.ClearCollectionCache()
|
||||||
|
if err := c.PopulateCollectionCache(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if printCollection {
|
||||||
|
v, err := c.CollectionJSON()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if csvTemplatePath != "" {
|
||||||
|
if err := c.WriteCSVTemplate(csvTemplatePath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.TestEditField()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user