add dalamud inventorytools integration (no features yet)
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
c4ce99ec7b
commit
10876ba1c6
|
@ -2,4 +2,7 @@ garlandtools-dump/item/*
|
|||
garlandtools-dump/output-data.json
|
||||
gilgetter.exe
|
||||
gilgetter
|
||||
build-dev.sh
|
||||
build-dev.sh
|
||||
inventorytools/client/configuration.go
|
||||
inventorytools/client/client.exe
|
||||
inventorytools/client/client
|
4
go.mod
4
go.mod
|
@ -1,3 +1,7 @@
|
|||
module code.mashffxiv.com/MashPotato/gilgetter
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/fsnotify/fsnotify v1.5.4
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
@ -2,12 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
inventorytools "code.mashffxiv.com/MashPotato/gilgetter/inventoryTools"
|
||||
)
|
||||
|
||||
// http - GET /
|
||||
|
@ -286,3 +289,33 @@ func staleItemsListHandler(w http.ResponseWriter, _ *http.Request) {
|
|||
}
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
// http - GET /inventory
|
||||
// http - POST /inventory
|
||||
// When POST, accepts a JSON object containing inventory data from the inventorytools-link utility
|
||||
// When GET, produces a list of the items in retainer inventory to take for listing on market
|
||||
func inventoryPageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
// TBD make actual page
|
||||
w.Write([]byte(fmt.Sprintf("%v", inventories)))
|
||||
|
||||
} else if r.Method == "POST" {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("failed to read request body on inventoryPageHandler: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
inventories = inventorytools.Inventories{}
|
||||
err = json.Unmarshal(body, &inventories)
|
||||
if err != nil {
|
||||
log.Printf("failed to unmarshal request body json to inventorytools.Inventories struct: %v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("processed inventory refresh")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# inventorytools
|
||||
|
||||
This is a library containing data model for integrating with the Dalamud plugin "InventoryTools". It comes bundled with the client-side uploader utility to upload your inventory data to gilgetter.
|
||||
|
||||
TBD: long term would make sense to make this whole functionality a standalone Dalamud plugin rather than piggy backing off another plugin which is uneccesary dependancy (subject to changing data model, become unsupported, etc). When this is done it probably makes sense to reverse the data flow - have gilgetter sync to the new dalamud plugin and move the UI to the plugin rather than UI being external web page served by gilgetter.
|
|
@ -0,0 +1,48 @@
|
|||
# inventorytools-client
|
||||
|
||||
This is the client
|
||||
|
||||
Monitors file for changes: %appdata%\XIVLauncher\pluginConfigs\InventoryTools\inventories.json
|
||||
|
||||
If any changes it will parse the file and then post the resulting json to an endpoint on gilgetter.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
To build, it requires creating a configuration.go (configuration in code because I'm super lazy) file in the client directory with the following:
|
||||
|
||||
```go
|
||||
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
inventorytools "code.mashffxiv.com/MashPotato/gilgetter/inventorytools"
|
||||
)
|
||||
|
||||
const (
|
||||
characterID = 18014498565524067 // set your own
|
||||
gilgetterURL = "https://gilgetter.mashffxiv.com/inventory" // set your own
|
||||
)
|
||||
|
||||
func setRetainers() {
|
||||
|
||||
retainers = make([]inventorytools.Retainer, 0)
|
||||
|
||||
retainer := inventorytools.Retainer{
|
||||
ID: 33777097240525369, // set your own
|
||||
Name: "Miamoore", // set your own
|
||||
MarketItemStorage: true, // set your own
|
||||
}
|
||||
retainers = append(retainers, retainer)
|
||||
|
||||
retainer = inventorytools.Retainer{
|
||||
ID: 33777097241150467, // set your own
|
||||
Name: "Superbe", // set your own
|
||||
MarketItemStorage: false, // set your own
|
||||
}
|
||||
retainers = append(retainers, retainer)
|
||||
}
|
||||
```
|
||||
|
||||
Then you simply run the utility and leave it running in the background. It will synchronize your character inventory, retainer bag inventories and retainer market inventories to gilgetter
|
|
@ -0,0 +1,167 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.mashffxiv.com/MashPotato/gilgetter/inventorytools"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var (
|
||||
lastUpdated time.Time
|
||||
inventories inventorytools.Inventories
|
||||
retainers []inventorytools.Retainer
|
||||
inventoriesFilePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
appDataDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
log.Fatal("Getting appdata directory failed:", err)
|
||||
}
|
||||
inventoriesFilePath = fmt.Sprintf("%s/XIVLauncher/pluginConfigs/InventoryTools/inventories.json", appDataDir)
|
||||
|
||||
setRetainers() // Create your own definitions that satisfy the retainer struct - see README.md
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// initial refresh and upload to gilgetter
|
||||
err := refreshFile()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to refresh file: %v", err)
|
||||
}
|
||||
|
||||
// watch file for changes and make subsequent uploads
|
||||
fileWatcher()
|
||||
}
|
||||
|
||||
func fileWatcher() {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal("NewWatcher failed: ", err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
for {
|
||||
log.Printf("watching file for changes")
|
||||
select {
|
||||
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Printf("%s %s\n", event.Name, event.Op)
|
||||
if event.Op == fsnotify.Write {
|
||||
err := refreshFile()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to refresh file: %v", err)
|
||||
}
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
log.Println("error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
err = watcher.Add(inventoriesFilePath)
|
||||
if err != nil {
|
||||
log.Fatal("Add failed:", err)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
func refreshFile() error {
|
||||
if time.Now().Before(lastUpdated.Add(time.Second * 3)) {
|
||||
return nil // Do not do anything if posted within 3 seconds
|
||||
}
|
||||
lastUpdated = time.Now()
|
||||
|
||||
// read and parse the InventoryTools state file
|
||||
fileBytes, err := os.ReadFile(inventoriesFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file inventories.json file: %v", err)
|
||||
}
|
||||
|
||||
err = unmarshalJSON(fileBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// marshall the transformed data and upload to gilgetter
|
||||
jsonBytes, err := json.Marshal(inventories)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal JSON: %v", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(jsonBytes)
|
||||
|
||||
resp, err := http.Post(gilgetterURL, "application/json", reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error posting json to gilgetter: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("error posting json to gilgetter: expected http status of '200' but got '%d'", resp.StatusCode)
|
||||
}
|
||||
|
||||
log.Printf("refreshed inventory with gilgetter")
|
||||
|
||||
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 {
|
||||
inventories = inventorytools.Inventories{}
|
||||
|
||||
// This is a map as the json keys are dynamic
|
||||
tmp := map[string]json.RawMessage{}
|
||||
err := json.Unmarshal(d, &tmp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal json to raw message: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal the character bags for the set character
|
||||
if _, ok := tmp[strconv.Itoa(characterID)]; !ok {
|
||||
return fmt.Errorf("unmarshaled json map has no key matching character ID '%d'", characterID)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(tmp[strconv.Itoa(characterID)], &inventories)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal character inventory: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal and transform the retainer bags and market postings
|
||||
for _, retainer := range retainers {
|
||||
|
||||
if _, ok := tmp[strconv.Itoa(retainer.ID)]; !ok {
|
||||
return fmt.Errorf("unmarshaled json map has no key matching retainer ID '%d'", retainer.ID)
|
||||
}
|
||||
|
||||
tmpRetainer := inventorytools.Retainer{}
|
||||
err = json.Unmarshal(tmp[strconv.Itoa(retainer.ID)], &tmpRetainer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal retainer '%s' with ID '%d': %v", retainer.Name, retainer.ID, err)
|
||||
}
|
||||
tmpRetainer.ID = retainer.ID
|
||||
tmpRetainer.Name = retainer.Name
|
||||
tmpRetainer.MarketItemStorage = retainer.MarketItemStorage
|
||||
inventories.Retainers = append(inventories.Retainers, tmpRetainer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package inventorytools
|
||||
|
||||
type Inventories struct {
|
||||
CharacterBags []Item `json:"CharacterBags,omitempty"`
|
||||
Retainers []Retainer `json:"Retainers,omitempty"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
ID int `json:"iid"`
|
||||
SortedContainer int `json:"sc"` // container/page 1-5 offset by 9999 for bags and (12000 for market?)
|
||||
SortedSlotIndex int `json:"ssi"` // sorted slot index 0-34
|
||||
Quantity int `json:"qty"`
|
||||
}
|
||||
|
||||
type Retainer struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MarketItemStorage bool `json:"marketitemstorage,omitempty"` // true if retainer is used to store items ready for listing on market
|
||||
RetainerBags []Item `json:"RetainerBags,omitempty"`
|
||||
RetainerMarket []Item `json:"RetainerMarket,omitempty"`
|
||||
}
|
||||
|
||||
|
4
main.go
4
main.go
|
@ -14,6 +14,8 @@ import (
|
|||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
inventorytools "code.mashffxiv.com/MashPotato/gilgetter/inventoryTools"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -31,6 +33,7 @@ var (
|
|||
world string // filter Universalis to this world name
|
||||
retainers []string // contains name of retainers
|
||||
craftingCheckoutItemIDs []int // The item IDs which were selected on the crafting checkout page, used for shopping list
|
||||
inventories inventorytools.Inventories
|
||||
)
|
||||
|
||||
// Object to represent a crafting material / ingredient in a recipe
|
||||
|
@ -195,6 +198,7 @@ func main() {
|
|||
http.HandleFunc("/", homePageHandler)
|
||||
http.HandleFunc("/stale-items", staleItemsPageHandler)
|
||||
http.HandleFunc("/crafting-checkout", craftingCheckoutPageHandler)
|
||||
http.HandleFunc("/inventory", inventoryPageHandler)
|
||||
|
||||
// API Handlers
|
||||
// Anything that is called by the front end but is not a web page
|
||||
|
|
Loading…
Reference in New Issue