diff --git a/graphql/stash-box/query.graphql b/graphql/stash-box/query.graphql index 72c764f1d..f7528e728 100644 --- a/graphql/stash-box/query.graphql +++ b/graphql/stash-box/query.graphql @@ -49,6 +49,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ...URLFragment } diff --git a/internal/manager/task_stash_box_tag.go b/internal/manager/task_stash_box_tag.go index 0574b29a5..093b53bff 100644 --- a/internal/manager/task_stash_box_tag.go +++ b/internal/manager/task_stash_box_tag.go @@ -9,6 +9,7 @@ import ( "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/performer" "github.com/stashapp/stash/pkg/scraper/stashbox" + "github.com/stashapp/stash/pkg/sliceutil" "github.com/stashapp/stash/pkg/studio" ) @@ -119,6 +120,18 @@ func (t *StashBoxBatchTagTask) findStashBoxPerformer(ctx context.Context) (*mode } if remoteID != "" { performer, err = client.FindStashBoxPerformerByID(ctx, remoteID) + + if performer != nil && performer.RemoteMergedIntoId != nil { + mergedPerformer, err := t.handleMergedPerformer(ctx, performer, client) + if err != nil { + return nil, err + } + + if mergedPerformer != nil { + logger.Infof("Performer id %s merged into %s, updating local performer", remoteID, *performer.RemoteMergedIntoId) + performer = mergedPerformer + } + } } } else { var name string @@ -133,6 +146,21 @@ func (t *StashBoxBatchTagTask) findStashBoxPerformer(ctx context.Context) (*mode return performer, err } +func (t *StashBoxBatchTagTask) handleMergedPerformer(ctx context.Context, performer *models.ScrapedPerformer, client *stashbox.Client) (mergedPerformer *models.ScrapedPerformer, err error) { + mergedPerformer, err = client.FindStashBoxPerformerByID(ctx, *performer.RemoteMergedIntoId) + if err != nil { + return nil, fmt.Errorf("loading merged performer %s from stashbox", *performer.RemoteMergedIntoId) + } + + if mergedPerformer.StoredID != nil && *mergedPerformer.StoredID != *performer.StoredID { + logger.Warnf("Performer %s merged into %s, but both exist locally, not merging", *performer.StoredID, *mergedPerformer.StoredID) + return nil, nil + } + + mergedPerformer.StoredID = performer.StoredID + return mergedPerformer, nil +} + func (t *StashBoxBatchTagTask) processMatchedPerformer(ctx context.Context, p *models.ScrapedPerformer, excluded map[string]bool) { // Refreshing an existing performer if t.performer != nil { @@ -156,6 +184,19 @@ func (t *StashBoxBatchTagTask) processMatchedPerformer(ctx context.Context, p *m partial := p.ToPartial(t.box.Endpoint, excluded, existingStashIDs) + // if we're setting the performer's aliases, and not the name, then filter out the name + // from the aliases to avoid duplicates + // add the name to the aliases if it's not already there + if partial.Aliases != nil && !partial.Name.Set { + partial.Aliases.Values = sliceutil.Filter(partial.Aliases.Values, func(s string) bool { + return s != t.performer.Name + }) + + if p.Name != nil && t.performer.Name != *p.Name { + partial.Aliases.Values = sliceutil.AppendUnique(partial.Aliases.Values, *p.Name) + } + } + if err := performer.ValidateUpdate(ctx, t.performer.ID, partial, qb); err != nil { return err } diff --git a/pkg/models/model_scraped_item.go b/pkg/models/model_scraped_item.go index 43e3e985b..846bd4e97 100644 --- a/pkg/models/model_scraped_item.go +++ b/pkg/models/model_scraped_item.go @@ -128,13 +128,15 @@ type ScrapedPerformer struct { Aliases *string `json:"aliases"` Tags []*ScrapedTag `json:"tags"` // This should be a base64 encoded data URL - Image *string `json:"image"` // deprecated: use Images - Images []string `json:"images"` - Details *string `json:"details"` - DeathDate *string `json:"death_date"` - HairColor *string `json:"hair_color"` - Weight *string `json:"weight"` - RemoteSiteID *string `json:"remote_site_id"` + Image *string `json:"image"` // deprecated: use Images + Images []string `json:"images"` + Details *string `json:"details"` + DeathDate *string `json:"death_date"` + HairColor *string `json:"hair_color"` + Weight *string `json:"weight"` + RemoteSiteID *string `json:"remote_site_id"` + RemoteDeleted bool `json:"remote_deleted"` + RemoteMergedIntoId *string `json:"remote_merged_into_id"` } func (ScrapedPerformer) IsScrapedContent() {} diff --git a/pkg/scraper/stashbox/graphql/generated_client.go b/pkg/scraper/stashbox/graphql/generated_client.go index b7586f171..8bc26071a 100644 --- a/pkg/scraper/stashbox/graphql/generated_client.go +++ b/pkg/scraper/stashbox/graphql/generated_client.go @@ -196,6 +196,8 @@ type PerformerFragment struct { Aliases []string "json:\"aliases\" graphql:\"aliases\"" Gender *GenderEnum "json:\"gender,omitempty\" graphql:\"gender\"" MergedIds []string "json:\"merged_ids\" graphql:\"merged_ids\"" + Deleted bool "json:\"deleted\" graphql:\"deleted\"" + MergedIntoID *string "json:\"merged_into_id,omitempty\" graphql:\"merged_into_id\"" Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" Images []*ImageFragment "json:\"images\" graphql:\"images\"" BirthDate *string "json:\"birth_date,omitempty\" graphql:\"birth_date\"" @@ -249,6 +251,18 @@ func (t *PerformerFragment) GetMergedIds() []string { } return t.MergedIds } +func (t *PerformerFragment) GetDeleted() bool { + if t == nil { + t = &PerformerFragment{} + } + return t.Deleted +} +func (t *PerformerFragment) GetMergedIntoID() *string { + if t == nil { + t = &PerformerFragment{} + } + return t.MergedIntoID +} func (t *PerformerFragment) GetUrls() []*URLFragment { if t == nil { t = &PerformerFragment{} @@ -860,6 +874,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -993,6 +1009,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -1126,6 +1144,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -1259,6 +1279,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -1331,6 +1353,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -1408,6 +1432,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } @@ -1546,6 +1572,8 @@ fragment PerformerFragment on Performer { aliases gender merged_ids + deleted + merged_into_id urls { ... URLFragment } diff --git a/pkg/scraper/stashbox/performer.go b/pkg/scraper/stashbox/performer.go index e3e2fb496..fe6246eec 100644 --- a/pkg/scraper/stashbox/performer.go +++ b/pkg/scraper/stashbox/performer.go @@ -297,16 +297,18 @@ func performerFragmentToScrapedPerformer(p graphql.PerformerFragment) *models.Sc } sp := &models.ScrapedPerformer{ - Name: &p.Name, - Disambiguation: p.Disambiguation, - Country: p.Country, - Measurements: formatMeasurements(*p.Measurements), - CareerLength: formatCareerLength(p.CareerStartYear, p.CareerEndYear), - Tattoos: formatBodyModifications(p.Tattoos), - Piercings: formatBodyModifications(p.Piercings), - Twitter: findURL(p.Urls, "TWITTER"), - RemoteSiteID: &p.ID, - Images: images, + Name: &p.Name, + Disambiguation: p.Disambiguation, + Country: p.Country, + Measurements: formatMeasurements(*p.Measurements), + CareerLength: formatCareerLength(p.CareerStartYear, p.CareerEndYear), + Tattoos: formatBodyModifications(p.Tattoos), + Piercings: formatBodyModifications(p.Piercings), + Twitter: findURL(p.Urls, "TWITTER"), + RemoteSiteID: &p.ID, + RemoteDeleted: p.Deleted, + RemoteMergedIntoId: p.MergedIntoID, + Images: images, // TODO - tags not currently supported // graphql schema change to accommodate this. Leave off for now. }