package plugins import ( "regexp" "slices" "github.com/navidrome/navidrome/core/agents" ) // Capability represents a plugin capability type. // Capabilities are detected by checking which functions a plugin exports. type Capability string // capabilityFunctions maps each capability to its required/optional functions. // A plugin has a capability if it exports at least one of these functions. var capabilityFunctions = map[Capability][]string{} // registerCapability registers a capability with its associated functions. func registerCapability(cap Capability, functions ...string) { capabilityFunctions[cap] = functions } // functionExistsChecker is an interface for checking if a function exists in a plugin. // This allows for testing without a real plugin instance. type functionExistsChecker interface { FunctionExists(name string) bool } // detectCapabilities detects which capabilities a plugin has by checking // which functions it exports. func detectCapabilities(plugin functionExistsChecker) []Capability { var capabilities []Capability for cap, functions := range capabilityFunctions { if slices.ContainsFunc(functions, plugin.FunctionExists) { capabilities = append(capabilities, cap) // Found at least one function, plugin has this capability } } return capabilities } // hasCapability checks if the given capabilities slice contains a specific capability. func hasCapability(capabilities []Capability, cap Capability) bool { return slices.Contains(capabilities, cap) } // retryLaterRe matches one capability's `(retry_later[:seconds])` token, which is // all a plugin fault carries back across the WASM boundary. The capability is part of the // pattern, so another capability's token in the same message cannot mask this one. The leading // \b keeps a superstring like `useragent(retry_later)` from matching `agent`. var ( agentRetryLaterRe = retryLaterRe("agent") scrobblerRetryLaterRe = retryLaterRe("scrobbler") ) func retryLaterRe(capability string) *regexp.Regexp { return regexp.MustCompile(`\b` + capability + `\(retry_later(?::(\d+))?\)`) } // parseRetryLater reports whether msg carries the capability's retry_later token, with its delay. func parseRetryLater(re *regexp.Regexp, msg string) (*agents.RetryLaterError, bool) { m := re.FindStringSubmatch(msg) if m == nil { return nil, false } return &agents.RetryLaterError{RetryIn: agents.ParseRetryIn(m[1])}, true }