allagan tools inventory data format CSV conversion
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Steven Polley 2023-11-24 19:22:47 -07:00
parent 6afa17fdaf
commit 9553f9c0a3
3 changed files with 206 additions and 94 deletions

View File

@ -748,12 +748,15 @@ func inventoryPageHandler(w http.ResponseWriter, r *http.Request) {
for _, retainer := range inventories.Retainers {
if retainer.RetainerMarket != nil {
for _, item := range retainer.RetainerMarket {
fmt.Printf("Item ID: %d\n", item.ID)
if item.ID == 0 {
itemsRequired++
}
}
itemsRequired += 20 - len(retainer.RetainerMarket)
// Update since conversion to CSV - no longer do we have nil entries
/*
for _, item := range retainer.RetainerMarket {
fmt.Printf("Item ID: %d\n", item.ID)
if item.ID == 0 {
itemsRequired++
}
}*/
} else {
log.Printf("retainer '%s' market inventory is nil, assuming no items posted?", retainer.Name)

View File

@ -0,0 +1,92 @@
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/fsnotify/fsnotify"
)
// Watches the downloads directory for autohotkey files and makes sure that only the newest one downloaded exists at a time
func downloadsFileWatcher() {
downloadsWatcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal("NewWatcher failed: ", err)
}
defer downloadsWatcher.Close()
done := make(chan bool)
go func() {
defer close(done)
defer log.Printf("file watcher go routine shutting down!") // could be from returns below?
for {
select {
case event, ok := <-downloadsWatcher.Events:
if !ok {
log.Printf("downloads watcher _event_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
return
}
if event.Op == fsnotify.Create {
if strings.Contains(event.Name, ".ahk") {
err = cleanDownloadsAHK()
if err != nil {
log.Printf("failed to clean downloads: %v", err)
}
}
}
case err, ok := <-downloadsWatcher.Errors:
if !ok {
log.Printf("downloads watcher _error_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
return
}
log.Printf("downloads watcher error: %v", err)
}
}
}()
err = downloadsWatcher.Add(downloadsFolderPath)
if err != nil {
log.Fatal("downloads watcher add down failed:", err)
}
<-done
}
func cleanDownloadsAHK() error {
err := filepath.Walk(downloadsFolderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk file: %v", err)
}
// ignore files that aren't AHK
if !strings.Contains(info.Name(), ".ahk") {
return nil
}
// ignore files downloaded in the past 5 minutes
if info.ModTime().After(time.Now().Add(time.Minute * -1)) {
return nil
}
log.Printf("removing old .ahk file - %s", info.Name())
err = os.Remove(fmt.Sprintf("%s/%s", downloadsFolderPath, info.Name()))
if err != nil {
return fmt.Errorf("failed to delete old .ahk '%s': %v", info.Name(), err)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to walk downloads directory: %v", err)
}
return nil
}

View File

@ -2,14 +2,13 @@ package main
import (
"bytes"
"encoding/csv"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"code.mashffxiv.deadbeef.codes/MashPotato/gilgetter/inventorytools"
@ -28,7 +27,7 @@ func init() {
if err != nil {
log.Fatal("Getting appdata directory failed:", err)
}
inventoriesFilePath = fmt.Sprintf("%s/XIVLauncher/pluginConfigs/GilGetterPlugin/inventories.json", appDataDir)
inventoriesFilePath = fmt.Sprintf("%s/XIVLauncher/pluginConfigs/GilGetterPlugin/inventories.csv", appDataDir)
setRetainers() // Create your own definitions that satisfy the retainer struct - see README.md
}
@ -113,16 +112,17 @@ func refreshFile() error {
}
lastUpdated = time.Now()
// read and parse the InventoryTools state file
fileBytes, err := os.ReadFile(inventoriesFilePath)
f, err := os.Open(inventoriesFilePath)
if err != nil {
return fmt.Errorf("failed to read file '%s': %v", inventoriesFilePath, err)
}
err = unmarshalJSON(fileBytes) // update global variables directly, no assignment here
parseInventoryCSV(f)
/*err = unmarshalJSON(fileBytes) // update global variables directly, no assignment here
if err != nil {
return fmt.Errorf("failed to unmarshal JSON in file '%s': %v", inventoriesFilePath, err)
}
}*/
// marshall the transformed data and upload to gilgetter
jsonBytes, err := json.Marshal(inventories)
@ -145,6 +145,102 @@ func refreshFile() error {
return nil
}
func parseInventoryCSV(f *os.File) error {
setRetainers() // reinitialize retainers from configuration
inventories = inventorytools.Inventories{CharacterBags: make([]inventorytools.Item, 0), Crystals: make([]inventorytools.Item, 0), Retainers: retainers}
r := csv.NewReader(f)
data, err := r.ReadAll()
if err != nil {
return fmt.Errorf("failed to read file with CSV reader: %v", err)
}
for i, row := range data {
/*
0 = container
1 = slot
2 = item id
3 = quantity
20 = sorted container
21 = sorted slot index
22 = retainer id
*/
item := inventorytools.Item{}
item.ID, err = strconv.Atoi(row[2])
if err != nil {
return fmt.Errorf("failed to convert item ID '%s' to int on csv row '%d': %v", row[2], i, err)
}
item.SortedContainer, err = strconv.Atoi(row[0])
if err != nil {
return fmt.Errorf("failed to convert sorted container '%s' to int on csv row '%d': %v", row[20], i, err)
}
item.SortedSlotIndex, err = strconv.Atoi(row[21])
if err != nil {
return fmt.Errorf("failed to convert sorted slot index '%s' to int on csv row '%d': %v", row[21], i, err)
}
item.Quantity, err = strconv.Atoi(row[3])
if err != nil {
return fmt.Errorf("failed to convert item quantity '%s' to int on csv row '%d': %v", row[3], i, err)
}
retID, err := strconv.Atoi(row[22])
if err != nil {
return fmt.Errorf("failed to convert retainerID '%s' to int on csv row '%d': %v", row[22], i, err)
}
// 0 - 4 = player bags (pages)
// 1000 = player equipped
// 2001 = player crystal bag
// 3200 - 3209 = armoury chest
// 10000 = retainer inventory page 1
// 10001 = retainer inventory page 2
// 10002 = retainer inventory page 3, etc
// 11000 = retainer equipped
// 12001 = retainer crystal bag
// 12002 = retainer market item
containerType, err := strconv.Atoi(row[0])
if err != nil {
return fmt.Errorf("failed to convert containerType '%s' to int on csv row '%d': %v", row[0], i, err)
}
if retID == characterID { // Handle Character items
if containerType < 4 { // player bags
inventories.CharacterBags = append(inventories.CharacterBags, item)
continue
}
if containerType == 2001 { // player crystals
inventories.Crystals = append(inventories.Crystals, item)
continue
}
}
retMatched := false
for i, retainer := range retainers {
if retID == retainer.ID {
retMatched = true
if containerType >= 10000 && containerType <= 10006 { // retainer inventory
inventories.Retainers[i].RetainerBags = append(inventories.Retainers[i].RetainerBags, item)
break
}
if containerType == 12002 { // market item
inventories.Retainers[i].RetainerMarket = append(inventories.Retainers[i].RetainerMarket, item)
break
}
fmt.Printf("Retainer matched but unhandled container: Container: %s | Slot: %s | Item ID: %s | Quantity: %s | SC: %s | SSI: %s | RET ID: %s\n", row[0], row[1], row[2], row[3], row[20], row[21], row[22])
}
}
if retMatched {
continue
}
fmt.Printf("UNHANDLED ITEM: Container: %s | Slot: %s | Item ID: %s | Quantity: %s | SC: %s | SSI: %s | RET ID: %s\n", row[0], row[1], row[2], row[3], row[20], row[21], row[22])
}
return nil
}
/*
// custom unmarshaller to make sense of the inventories.json file
// why the fuck do people use dynamic keys in json instead of structuring their data with static definitions
func unmarshalJSON(d []byte) error {
@ -158,7 +254,7 @@ func unmarshalJSON(d []byte) error {
}
// Unmarshal the character bags and crystals for the set character
if _, ok := tmp[strconv.Itoa(characterID)]; !ok {
if _, ok := tmp[strconv.Itoa(characterID)]; !ok {x
return fmt.Errorf("unmarshaled json map has no key matching character ID '%d'", characterID)
}
@ -187,83 +283,4 @@ func unmarshalJSON(d []byte) error {
return nil
}
// Watches the downloads directory for autohotkey files and makes sure that only the newest one downloaded exists at a time
func downloadsFileWatcher() {
downloadsWatcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal("NewWatcher failed: ", err)
}
defer downloadsWatcher.Close()
done := make(chan bool)
go func() {
defer close(done)
defer log.Printf("file watcher go routine shutting down!") // could be from returns below?
for {
select {
case event, ok := <-downloadsWatcher.Events:
if !ok {
log.Printf("downloads watcher _event_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
return
}
if event.Op == fsnotify.Create {
if strings.Contains(event.Name, ".ahk") {
err = cleanDownloadsAHK()
if err != nil {
log.Printf("failed to clean downloads: %v", err)
}
}
}
case err, ok := <-downloadsWatcher.Errors:
if !ok {
log.Printf("downloads watcher _error_ NOT OK TERMINATING ARRRGGHGHH: %v", err)
return
}
log.Printf("downloads watcher error: %v", err)
}
}
}()
err = downloadsWatcher.Add(downloadsFolderPath)
if err != nil {
log.Fatal("downloads watcher add down failed:", err)
}
<-done
}
func cleanDownloadsAHK() error {
err := filepath.Walk(downloadsFolderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk file: %v", err)
}
// ignore files that aren't AHK
if !strings.Contains(info.Name(), ".ahk") {
return nil
}
// ignore files downloaded in the past 5 minutes
if info.ModTime().After(time.Now().Add(time.Minute * -1)) {
return nil
}
log.Printf("removing old .ahk file - %s", info.Name())
err = os.Remove(fmt.Sprintf("%s/%s", downloadsFolderPath, info.Name()))
if err != nil {
return fmt.Errorf("failed to delete old .ahk '%s': %v", info.Name(), err)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to walk downloads directory: %v", err)
}
return nil
}
*/