| package engine |
|
|
| import ( |
| goerr "errors" |
| "fmt" |
| "sort" |
| "sync" |
|
|
| "golang.org/x/text/language" |
| "gorm.io/gorm/clause" |
|
|
| "github.com/metatube-community/metatube-sdk-go/collection/sets" |
| "github.com/metatube-community/metatube-sdk-go/collection/slices" |
| "github.com/metatube-community/metatube-sdk-go/common/comparer" |
| "github.com/metatube-community/metatube-sdk-go/common/parser" |
| "github.com/metatube-community/metatube-sdk-go/engine/providerid" |
| "github.com/metatube-community/metatube-sdk-go/model" |
| mt "github.com/metatube-community/metatube-sdk-go/provider" |
| "github.com/metatube-community/metatube-sdk-go/provider/gfriends" |
| ) |
|
|
| func (e *Engine) searchActorFromDB(keyword string, provider mt.Provider) (results []*model.ActorSearchResult, err error) { |
| var infos []*model.ActorInfo |
| if err = e.db. |
| Where("provider = ? AND name = ? COLLATE NOCASE", |
| provider.Name(), keyword). |
| Find(&infos).Error; err == nil { |
| for _, info := range infos { |
| if !info.IsValid() { |
| continue |
| } |
| results = append(results, info.ToSearchResult()) |
| } |
| } |
| return |
| } |
|
|
| func (e *Engine) searchActor(keyword string, provider mt.Provider, fallback bool) ([]*model.ActorSearchResult, error) { |
| innerSearch := func(keyword string) (results []*model.ActorSearchResult, err error) { |
| if provider.Name() == gfriends.Name { |
| return provider.(mt.ActorSearcher).SearchActor(keyword) |
| } |
| if searcher, ok := provider.(mt.ActorSearcher); ok { |
| defer func() { |
| if err != nil || len(results) == 0 { |
| return |
| } |
| const minSimilarity = 0.3 |
| ps := new(slices.WeightedSlice[*model.ActorSearchResult, float64]) |
| for _, result := range results { |
| if similarity := comparer.Compare(result.Name, keyword); similarity >= minSimilarity { |
| ps.Append(result, similarity) |
| } |
| } |
| results = ps.SortFunc(sort.Stable).Slice() |
| }() |
| if fallback { |
| defer func() { |
| if innerResults, innerErr := e.searchActorFromDB(keyword, provider); |
| |
| innerErr == nil && len(innerResults) > 0 { |
| |
| err = nil |
| |
| asr := sets.NewOrderedSetWithHash(func(v *model.ActorSearchResult) string { return v.Provider + v.ID }) |
| |
| |
| asr.Add(innerResults...) |
| asr.Add(results...) |
| results = asr.AsSlice() |
| } |
| }() |
| } |
| return searcher.SearchActor(keyword) |
| } |
| |
| return nil, mt.ErrInfoNotFound |
| } |
| names := parser.ParseActorNames(keyword) |
| if len(names) == 0 { |
| return nil, mt.ErrInvalidKeyword |
| } |
| var ( |
| results []*model.ActorSearchResult |
| errors []error |
| ) |
| for _, name := range names { |
| innerResults, innerErr := innerSearch(name) |
| if innerErr != nil && |
| |
| !goerr.Is(innerErr, mt.ErrInfoNotFound) { |
| |
| errors = append(errors, innerErr) |
| continue |
| } |
| results = append(results, innerResults...) |
| } |
| if len(results) == 0 { |
| if len(errors) > 0 { |
| return nil, fmt.Errorf("search errors: %v", errors) |
| } |
| return nil, mt.ErrInfoNotFound |
| } |
| return results, nil |
| } |
|
|
| func (e *Engine) SearchActor(keyword, name string, fallback bool) ([]*model.ActorSearchResult, error) { |
| provider, err := e.GetActorProviderByName(name) |
| if err != nil { |
| return nil, err |
| } |
| return e.searchActor(keyword, provider, fallback) |
| } |
|
|
| func (e *Engine) SearchActorAll(keyword string, fallback bool) (results []*model.ActorSearchResult, err error) { |
| var ( |
| mu sync.Mutex |
| wg sync.WaitGroup |
| ) |
| for _, provider := range e.actorProviders.Iterator() { |
| wg.Add(1) |
| go func(provider mt.ActorProvider) { |
| defer wg.Done() |
| if innerResults, innerErr := e.searchActor(keyword, provider, fallback); innerErr == nil { |
| for _, result := range innerResults { |
| if result.IsValid() { |
| mu.Lock() |
| results = append(results, result) |
| mu.Unlock() |
| } |
| } |
| } |
| }(provider) |
| } |
| wg.Wait() |
|
|
| sort.SliceStable(results, func(i, j int) bool { |
| return e.MustGetActorProviderByName(results[i].Provider).Priority() > |
| e.MustGetActorProviderByName(results[j].Provider).Priority() |
| }) |
| return |
| } |
|
|
| func (e *Engine) getActorInfoFromDB(provider mt.ActorProvider, id string) (*model.ActorInfo, error) { |
| info := &model.ActorInfo{} |
| err := e.db. |
| Where("provider = ?", provider.Name()). |
| Where("id = ? COLLATE NOCASE", id). |
| First(info).Error |
| return info, err |
| } |
|
|
| func (e *Engine) getActorInfoWithCallback(provider mt.ActorProvider, id string, lazy bool, callback func() (*model.ActorInfo, error)) (info *model.ActorInfo, err error) { |
| defer func() { |
| |
| if err == nil && (info == nil || !info.IsValid()) { |
| err = mt.ErrIncompleteMetadata |
| } |
| }() |
| if provider.Name() == gfriends.Name { |
| return provider.GetActorInfoByID(id) |
| } |
| defer func() { |
| |
| if err == nil && info != nil && provider.Language() == language.Japanese { |
| if gInfo, gErr := e.MustGetActorProviderByName(gfriends.Name).GetActorInfoByID(info.Name); gErr == nil && len(gInfo.Images) > 0 { |
| info.Images = append(gInfo.Images, info.Images...) |
| } |
| } |
| }() |
| |
| if lazy { |
| if info, err = e.getActorInfoFromDB(provider, id); err == nil && info.IsValid() { |
| return |
| } |
| } |
| |
| defer func() { |
| if err == nil && info.IsValid() { |
| |
| e.db.Clauses(clause.OnConflict{ |
| UpdateAll: true, |
| }).Create(info) |
| } |
| }() |
| return callback() |
| } |
|
|
| func (e *Engine) getActorInfoByProviderID(provider mt.ActorProvider, id string, lazy bool) (*model.ActorInfo, error) { |
| if id = provider.NormalizeActorID(id); id == "" { |
| return nil, mt.ErrInvalidID |
| } |
| return e.getActorInfoWithCallback(provider, id, lazy, func() (*model.ActorInfo, error) { |
| return provider.GetActorInfoByID(id) |
| }) |
| } |
|
|
| func (e *Engine) GetActorInfoByProviderID(pid providerid.ProviderID, lazy bool) (*model.ActorInfo, error) { |
| provider, err := e.GetActorProviderByName(pid.Provider) |
| if err != nil { |
| return nil, err |
| } |
| return e.getActorInfoByProviderID(provider, pid.ID, lazy) |
| } |
|
|
| func (e *Engine) getActorInfoByProviderURL(provider mt.ActorProvider, rawURL string, lazy bool) (*model.ActorInfo, error) { |
| id, err := provider.ParseActorIDFromURL(rawURL) |
| switch { |
| case err != nil: |
| return nil, err |
| case id == "": |
| return nil, mt.ErrInvalidURL |
| } |
| return e.getActorInfoWithCallback(provider, id, lazy, func() (*model.ActorInfo, error) { |
| return provider.GetActorInfoByURL(rawURL) |
| }) |
| } |
|
|
| func (e *Engine) GetActorInfoByURL(rawURL string, lazy bool) (*model.ActorInfo, error) { |
| provider, err := e.GetActorProviderByURL(rawURL) |
| if err != nil { |
| return nil, err |
| } |
| return e.getActorInfoByProviderURL(provider, rawURL, lazy) |
| } |
|
|