mirror of
https://github.com/navidrome/navidrome.git
synced 2025-12-23 23:18:05 -05:00
feat(plugins): add Wikimedia plugin for Navidrome to fetch artist metadata
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
18
plugins/examples/Makefile
Normal file
18
plugins/examples/Makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
# Build example plugins for Navidrome
|
||||
# Auto-discover all plugin folders (folders containing go.mod)
|
||||
PLUGINS := $(patsubst %/go.mod,%,$(wildcard */go.mod))
|
||||
|
||||
# Prefer tinygo if available, it produces smaller wasm binaries.
|
||||
TINYGO := $(shell command -v tinygo 2> /dev/null)
|
||||
|
||||
all: $(PLUGINS:%=%.wasm)
|
||||
|
||||
clean:
|
||||
rm -f $(PLUGINS:%=%.wasm)
|
||||
|
||||
%.wasm: %/main.go %/go.mod
|
||||
ifdef TINYGO
|
||||
cd $* && tinygo build -target wasip1 -buildmode=c-shared -o ../$@ .
|
||||
else
|
||||
cd $* && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ../$@ .
|
||||
endif
|
||||
87
plugins/examples/README.md
Normal file
87
plugins/examples/README.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Navidrome Plugin Examples
|
||||
|
||||
This folder contains example plugins for Navidrome that demonstrate how to build metadata agents using the plugin system.
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [TinyGo](https://tinygo.org/getting-started/install/) (recommended) or Go 1.23+
|
||||
- [Extism CLI](https://extism.org/docs/install) (optional, for testing)
|
||||
|
||||
### Build all plugins
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
This will compile all example plugins and place the `.wasm` files in this directory.
|
||||
|
||||
### Build a specific plugin
|
||||
|
||||
```bash
|
||||
make minimal.wasm
|
||||
make wikimedia.wasm
|
||||
```
|
||||
|
||||
### Clean build artifacts
|
||||
|
||||
```bash
|
||||
make clean
|
||||
```
|
||||
|
||||
## Available Examples
|
||||
|
||||
| Plugin | Description |
|
||||
|-------------------------|---------------------------------------------------------------|
|
||||
| [minimal](minimal/) | A minimal example showing the basic plugin structure |
|
||||
| [wikimedia](wikimedia/) | Fetches artist metadata from Wikidata, DBpedia, and Wikipedia |
|
||||
|
||||
## Testing with Extism CLI
|
||||
|
||||
You can test any plugin using the Extism CLI:
|
||||
|
||||
```bash
|
||||
# Test the manifest
|
||||
extism call minimal.wasm nd_manifest --wasi
|
||||
|
||||
# Test with input
|
||||
extism call minimal.wasm nd_get_artist_biography --wasi \
|
||||
--input '{"id":"1","name":"The Beatles"}'
|
||||
```
|
||||
|
||||
For plugins that make HTTP requests, use `--allow-host` to permit access:
|
||||
|
||||
```bash
|
||||
extism call wikimedia.wasm nd_get_artist_url --wasi \
|
||||
--input '{"id":"1","name":"The Beatles"}' \
|
||||
--allow-host "query.wikidata.org"
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Copy any `.wasm` file to your Navidrome plugins folder:
|
||||
|
||||
```bash
|
||||
cp minimal.wasm /path/to/navidrome/plugins/
|
||||
```
|
||||
|
||||
Then enable plugins in your `navidrome.toml`:
|
||||
|
||||
```toml
|
||||
[Plugins]
|
||||
Enabled = true
|
||||
Folder = "/path/to/navidrome/plugins"
|
||||
```
|
||||
|
||||
And add the plugin to your agents list:
|
||||
|
||||
```toml
|
||||
Agents = "lastfm,spotify,wikimedia"
|
||||
```
|
||||
|
||||
## Creating Your Own Plugin
|
||||
|
||||
See the [minimal](minimal/) example for the simplest starting point, or [wikimedia](wikimedia/) for a more complete example with HTTP requests.
|
||||
|
||||
For full documentation, see the [Plugin System README](../README.md).
|
||||
137
plugins/examples/wikimedia/README.md
Normal file
137
plugins/examples/wikimedia/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Wikimedia Plugin for Navidrome
|
||||
|
||||
A Navidrome plugin that fetches artist metadata from Wikidata, DBpedia, and Wikipedia.
|
||||
|
||||
## Features
|
||||
|
||||
- **Artist URL**: Fetches Wikipedia URL for an artist using Wikidata (by MBID or name), DBpedia, or falls back to a Wikipedia search URL
|
||||
- **Artist Biography**: Fetches the introductory text from the artist's Wikipedia page
|
||||
- **Artist Images**: Fetches artist images from Wikidata
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [TinyGo](https://tinygo.org/getting-started/install/) (recommended) or Go 1.23+
|
||||
|
||||
### Build using the Makefile (recommended)
|
||||
|
||||
```bash
|
||||
cd plugins/examples
|
||||
make wikimedia.wasm
|
||||
```
|
||||
|
||||
### Build manually with TinyGo
|
||||
|
||||
```bash
|
||||
cd plugins/examples/wikimedia
|
||||
tinygo build -target wasip1 -buildmode=c-shared -o ../wikimedia.wasm .
|
||||
```
|
||||
|
||||
### Build manually with Go
|
||||
|
||||
```bash
|
||||
cd plugins/examples/wikimedia
|
||||
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ../wikimedia.wasm .
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Copy `wikimedia.wasm` from the examples folder to your Navidrome plugins folder:
|
||||
|
||||
```bash
|
||||
cp plugins/examples/wikimedia.wasm /path/to/navidrome/plugins/
|
||||
```
|
||||
|
||||
Then enable plugins in your `navidrome.toml`:
|
||||
|
||||
```toml
|
||||
[Plugins]
|
||||
Enabled = true
|
||||
Folder = "/path/to/navidrome/plugins"
|
||||
```
|
||||
|
||||
Add the plugin to your agents list:
|
||||
|
||||
```toml
|
||||
Agents = "lastfm,spotify,wikimedia"
|
||||
```
|
||||
|
||||
## Testing with Extism CLI
|
||||
|
||||
Install the [Extism CLI](https://extism.org/docs/install):
|
||||
|
||||
```bash
|
||||
brew install extism/tap/extism # macOS
|
||||
# or see https://extism.org/docs/install for other platforms
|
||||
```
|
||||
|
||||
Run these commands from the `plugins/examples` directory.
|
||||
|
||||
### Test the manifest
|
||||
|
||||
```bash
|
||||
extism call wikimedia.wasm nd_manifest --wasi
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```json
|
||||
{"name":"Wikimedia","author":"Navidrome","version":"1.0.0","description":"Fetches artist metadata from Wikidata, DBpedia and Wikipedia","website":"https://navidrome.org","permissions":{"http":{"reason":"Fetch metadata from Wikimedia APIs","allowedHosts":["query.wikidata.org","dbpedia.org","en.wikipedia.org"]}}}
|
||||
```
|
||||
|
||||
### Test artist URL lookup
|
||||
|
||||
```bash
|
||||
# With MBID (The Beatles)
|
||||
extism call wikimedia.wasm nd_get_artist_url --wasi \
|
||||
--input '{"id":"1","name":"The Beatles","mbid":"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"}' \
|
||||
--allow-host "query.wikidata.org"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```json
|
||||
{"url":"https://en.wikipedia.org/wiki/The_Beatles"}
|
||||
```
|
||||
|
||||
### Test artist biography
|
||||
|
||||
```bash
|
||||
extism call wikimedia.wasm nd_get_artist_biography --wasi \
|
||||
--input '{"id":"1","name":"The Beatles","mbid":"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"}' \
|
||||
--allow-host "query.wikidata.org" \
|
||||
--allow-host "en.wikipedia.org"
|
||||
```
|
||||
|
||||
### Test artist images
|
||||
|
||||
```bash
|
||||
extism call wikimedia.wasm nd_get_artist_images --wasi \
|
||||
--input '{"id":"1","name":"The Beatles","mbid":"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d"}' \
|
||||
--allow-host "query.wikidata.org"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```json
|
||||
{"images":[{"url":"http://commons.wikimedia.org/wiki/Special:FilePath/Beatles%20ad%201965%20just%20the%20beatles%20crop.jpg","size":0}]}
|
||||
```
|
||||
|
||||
## API Endpoints Used
|
||||
|
||||
| Service | Endpoint | Purpose |
|
||||
|---------|----------|---------|
|
||||
| Wikidata | `https://query.wikidata.org/sparql` | SPARQL queries for Wikipedia URLs and images |
|
||||
| DBpedia | `https://dbpedia.org/sparql` | Fallback SPARQL queries for Wikipedia URLs and short bios |
|
||||
| Wikipedia | `https://en.wikipedia.org/w/api.php` | MediaWiki API for article extracts |
|
||||
|
||||
## Implemented Functions
|
||||
|
||||
| Function | Description |
|
||||
|----------|-------------|
|
||||
| `nd_manifest` | Returns plugin manifest with HTTP permissions |
|
||||
| `nd_get_artist_url` | Returns Wikipedia URL for an artist |
|
||||
| `nd_get_artist_biography` | Returns artist biography from Wikipedia |
|
||||
| `nd_get_artist_images` | Returns artist image URLs from Wikidata |
|
||||
|
||||
## License
|
||||
|
||||
Same as Navidrome - GPL-3.0
|
||||
5
plugins/examples/wikimedia/go.mod
Normal file
5
plugins/examples/wikimedia/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module wikimedia-plugin
|
||||
|
||||
go 1.23
|
||||
|
||||
require github.com/extism/go-pdk v1.1.3
|
||||
2
plugins/examples/wikimedia/go.sum
Normal file
2
plugins/examples/wikimedia/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/extism/go-pdk v1.1.3 h1:hfViMPWrqjN6u67cIYRALZTZLk/enSPpNKa+rZ9X2SQ=
|
||||
github.com/extism/go-pdk v1.1.3/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
|
||||
458
plugins/examples/wikimedia/main.go
Normal file
458
plugins/examples/wikimedia/main.go
Normal file
@@ -0,0 +1,458 @@
|
||||
// Wikimedia plugin for Navidrome - fetches artist metadata from Wikidata, DBpedia and Wikipedia.
|
||||
//
|
||||
// Build with:
|
||||
//
|
||||
// tinygo build -o wikimedia.wasm -target wasip1 -buildmode=c-shared ./main.go
|
||||
//
|
||||
// Install by copying wikimedia.wasm to your Navidrome plugins folder.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/extism/go-pdk"
|
||||
)
|
||||
|
||||
const (
|
||||
wikidataEndpoint = "https://query.wikidata.org/sparql"
|
||||
dbpediaEndpoint = "https://dbpedia.org/sparql"
|
||||
mediawikiAPIEndpoint = "https://en.wikipedia.org/w/api.php"
|
||||
)
|
||||
|
||||
// Manifest types
|
||||
type Manifest struct {
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Version string `json:"version"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website,omitempty"`
|
||||
Permissions *Permissions `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
HTTP *HTTPPermission `json:"http,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPPermission struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
AllowedHosts []string `json:"allowedHosts,omitempty"`
|
||||
}
|
||||
|
||||
// Input types
|
||||
type ArtistInput struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MBID string `json:"mbid,omitempty"`
|
||||
}
|
||||
|
||||
// Output types
|
||||
type URLOutput struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type BiographyOutput struct {
|
||||
Biography string `json:"biography"`
|
||||
}
|
||||
|
||||
type ImageInfo struct {
|
||||
URL string `json:"url"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type ImagesOutput struct {
|
||||
Images []ImageInfo `json:"images"`
|
||||
}
|
||||
|
||||
// SPARQL response types
|
||||
type SPARQLResult struct {
|
||||
Results struct {
|
||||
Bindings []SPARQLBinding `json:"bindings"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
type SPARQLBinding struct {
|
||||
Sitelink *SPARQLValue `json:"sitelink,omitempty"`
|
||||
Wiki *SPARQLValue `json:"wiki,omitempty"`
|
||||
Comment *SPARQLValue `json:"comment,omitempty"`
|
||||
Img *SPARQLValue `json:"img,omitempty"`
|
||||
}
|
||||
|
||||
type SPARQLValue struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// MediaWiki API response types
|
||||
type MediaWikiExtractResult struct {
|
||||
Query struct {
|
||||
Pages map[string]MediaWikiPage `json:"pages"`
|
||||
} `json:"query"`
|
||||
}
|
||||
|
||||
type MediaWikiPage struct {
|
||||
PageID int `json:"pageid"`
|
||||
Ns int `json:"ns"`
|
||||
Title string `json:"title"`
|
||||
Extract string `json:"extract"`
|
||||
Missing bool `json:"missing"`
|
||||
}
|
||||
|
||||
//go:wasmexport nd_manifest
|
||||
func ndManifest() int32 {
|
||||
manifest := Manifest{
|
||||
Name: "Wikimedia",
|
||||
Author: "Navidrome",
|
||||
Version: "1.0.0",
|
||||
Description: "Fetches artist metadata from Wikidata, DBpedia and Wikipedia",
|
||||
Website: "https://navidrome.org",
|
||||
Permissions: &Permissions{
|
||||
HTTP: &HTTPPermission{
|
||||
Reason: "Fetch metadata from Wikimedia APIs",
|
||||
AllowedHosts: []string{
|
||||
"query.wikidata.org",
|
||||
"dbpedia.org",
|
||||
"en.wikipedia.org",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
out, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
pdk.Output(out)
|
||||
return 0
|
||||
}
|
||||
|
||||
// sparqlQuery executes a SPARQL query and returns the result
|
||||
func sparqlQuery(endpoint, query string) (*SPARQLResult, error) {
|
||||
form := url.Values{}
|
||||
form.Set("query", query)
|
||||
|
||||
req := pdk.NewHTTPRequest(pdk.MethodPost, endpoint)
|
||||
req.SetHeader("Accept", "application/sparql-results+json")
|
||||
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetHeader("User-Agent", "NavidromeWikimediaPlugin/1.0")
|
||||
req.SetBody([]byte(form.Encode()))
|
||||
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("SPARQL query to %s: %s", endpoint, query))
|
||||
|
||||
resp := req.Send()
|
||||
if resp.Status() != 200 {
|
||||
return nil, fmt.Errorf("SPARQL HTTP error: status %d", resp.Status())
|
||||
}
|
||||
|
||||
var result SPARQLResult
|
||||
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse SPARQL response: %w", err)
|
||||
}
|
||||
if len(result.Results.Bindings) == 0 {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// mediawikiQuery executes a MediaWiki API query
|
||||
func mediawikiQuery(params url.Values) ([]byte, error) {
|
||||
apiURL := fmt.Sprintf("%s?%s", mediawikiAPIEndpoint, params.Encode())
|
||||
|
||||
req := pdk.NewHTTPRequest(pdk.MethodGet, apiURL)
|
||||
req.SetHeader("Accept", "application/json")
|
||||
req.SetHeader("User-Agent", "NavidromeWikimediaPlugin/1.0")
|
||||
|
||||
resp := req.Send()
|
||||
if resp.Status() != 200 {
|
||||
return nil, fmt.Errorf("MediaWiki HTTP error: status %d", resp.Status())
|
||||
}
|
||||
return resp.Body(), nil
|
||||
}
|
||||
|
||||
// getWikidataWikipediaURL fetches the Wikipedia URL from Wikidata using MBID or name
|
||||
func getWikidataWikipediaURL(mbid, name string) (string, error) {
|
||||
var q string
|
||||
if mbid != "" {
|
||||
q = fmt.Sprintf(`SELECT ?sitelink WHERE { ?artist wdt:P434 "%s". ?sitelink schema:about ?artist; schema:isPartOf <https://en.wikipedia.org/>. } LIMIT 1`, mbid)
|
||||
} else if name != "" {
|
||||
escapedName := strings.ReplaceAll(name, "\"", "\\\"")
|
||||
q = fmt.Sprintf(`SELECT ?sitelink WHERE { ?artist rdfs:label "%s"@en. ?sitelink schema:about ?artist; schema:isPartOf <https://en.wikipedia.org/>. } LIMIT 1`, escapedName)
|
||||
} else {
|
||||
return "", fmt.Errorf("MBID or Name required for Wikidata URL lookup")
|
||||
}
|
||||
|
||||
result, err := sparqlQuery(wikidataEndpoint, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if result.Results.Bindings[0].Sitelink != nil {
|
||||
return result.Results.Bindings[0].Sitelink.Value, nil
|
||||
}
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// getDBpediaWikipediaURL fetches the Wikipedia URL from DBpedia using name
|
||||
func getDBpediaWikipediaURL(name string) (string, error) {
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
escapedName := strings.ReplaceAll(name, "\"", "\\\"")
|
||||
q := fmt.Sprintf(`SELECT ?wiki WHERE { ?artist foaf:name "%s"@en; foaf:isPrimaryTopicOf ?wiki. FILTER regex(str(?wiki), "^https://en.wikipedia.org/") } LIMIT 1`, escapedName)
|
||||
|
||||
result, err := sparqlQuery(dbpediaEndpoint, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if result.Results.Bindings[0].Wiki != nil {
|
||||
return result.Results.Bindings[0].Wiki.Value, nil
|
||||
}
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// getDBpediaComment fetches the DBpedia comment (short bio) for an artist
|
||||
func getDBpediaComment(name string) (string, error) {
|
||||
if name == "" {
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
escapedName := strings.ReplaceAll(name, "\"", "\\\"")
|
||||
q := fmt.Sprintf(`SELECT ?comment WHERE { ?artist foaf:name "%s"@en; rdfs:comment ?comment. FILTER (lang(?comment) = 'en') } LIMIT 1`, escapedName)
|
||||
|
||||
result, err := sparqlQuery(dbpediaEndpoint, q)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if result.Results.Bindings[0].Comment != nil {
|
||||
return result.Results.Bindings[0].Comment.Value, nil
|
||||
}
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// getWikipediaExtract fetches the intro text from Wikipedia
|
||||
func getWikipediaExtract(pageTitle string) (string, error) {
|
||||
if pageTitle == "" {
|
||||
return "", fmt.Errorf("page title required")
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Set("action", "query")
|
||||
params.Set("format", "json")
|
||||
params.Set("prop", "extracts")
|
||||
params.Set("exintro", "true")
|
||||
params.Set("explaintext", "true")
|
||||
params.Set("titles", pageTitle)
|
||||
params.Set("redirects", "1")
|
||||
|
||||
body, err := mediawikiQuery(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var result MediaWikiExtractResult
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return "", fmt.Errorf("failed to parse MediaWiki response: %w", err)
|
||||
}
|
||||
|
||||
for _, page := range result.Query.Pages {
|
||||
if page.Missing {
|
||||
continue
|
||||
}
|
||||
if page.Extract != "" {
|
||||
return strings.TrimSpace(page.Extract), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// extractPageTitleFromURL extracts the page title from a Wikipedia URL
|
||||
func extractPageTitleFromURL(wikiURL string) (string, error) {
|
||||
parsedURL, err := url.Parse(wikiURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if parsedURL.Host != "en.wikipedia.org" {
|
||||
return "", fmt.Errorf("URL host is not en.wikipedia.org: %s", parsedURL.Host)
|
||||
}
|
||||
pathParts := strings.Split(strings.TrimPrefix(parsedURL.Path, "/"), "/")
|
||||
if len(pathParts) < 2 || pathParts[0] != "wiki" {
|
||||
return "", fmt.Errorf("URL path does not match /wiki/<title> format: %s", parsedURL.Path)
|
||||
}
|
||||
title := pathParts[1]
|
||||
if title == "" {
|
||||
return "", fmt.Errorf("extracted title is empty")
|
||||
}
|
||||
decodedTitle, err := url.PathUnescape(title)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode title '%s': %w", title, err)
|
||||
}
|
||||
return decodedTitle, nil
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_url
|
||||
func ndGetArtistURL() int32 {
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistURL: name=%s, mbid=%s", input.Name, input.MBID))
|
||||
|
||||
// 1. Try Wikidata (MBID first, then name)
|
||||
wikiURL, err := getWikidataWikipediaURL(input.MBID, input.Name)
|
||||
if err == nil && wikiURL != "" {
|
||||
output := URLOutput{URL: wikiURL}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if err != nil {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Wikidata URL failed: %v", err))
|
||||
}
|
||||
|
||||
// 2. Try DBpedia (Name only)
|
||||
if input.Name != "" {
|
||||
wikiURL, err = getDBpediaWikipediaURL(input.Name)
|
||||
if err == nil && wikiURL != "" {
|
||||
output := URLOutput{URL: wikiURL}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if err != nil {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("DBpedia URL failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to search URL
|
||||
if input.Name != "" {
|
||||
searchURL := fmt.Sprintf("https://en.wikipedia.org/w/index.php?search=%s", url.QueryEscape(input.Name))
|
||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("URL not found, falling back to search URL: %s", searchURL))
|
||||
output := URLOutput{URL: searchURL}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
pdk.SetErrorString("could not determine Wikipedia URL")
|
||||
return 1
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_biography
|
||||
func ndGetArtistBiography() int32 {
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistBiography: name=%s, mbid=%s", input.Name, input.MBID))
|
||||
|
||||
// 1. Get Wikipedia URL (using the logic from GetArtistURL)
|
||||
wikiURL := ""
|
||||
tempURL, wdErr := getWikidataWikipediaURL(input.MBID, input.Name)
|
||||
if wdErr == nil && tempURL != "" {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Found Wikidata URL: %s", tempURL))
|
||||
wikiURL = tempURL
|
||||
} else if input.Name != "" {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Wikidata URL failed (%v), trying DBpedia", wdErr))
|
||||
tempURL, dbErr := getDBpediaWikipediaURL(input.Name)
|
||||
if dbErr == nil && tempURL != "" {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Found DBpedia URL: %s", tempURL))
|
||||
wikiURL = tempURL
|
||||
} else {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("DBpedia URL failed: %v", dbErr))
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If Wikipedia URL found, try MediaWiki API
|
||||
if wikiURL != "" {
|
||||
pageTitle, err := extractPageTitleFromURL(wikiURL)
|
||||
if err == nil {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Extracted page title: %s", pageTitle))
|
||||
bio, err := getWikipediaExtract(pageTitle)
|
||||
if err == nil && bio != "" {
|
||||
pdk.Log(pdk.LogDebug, "Found Wikipedia extract")
|
||||
output := BiographyOutput{Biography: bio}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Wikipedia extract failed: %v", err))
|
||||
} else {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Error extracting page title from URL '%s': %v", wikiURL, err))
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fallback to DBpedia Comment (Name only)
|
||||
if input.Name != "" {
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("Falling back to DBpedia comment for name: %s", input.Name))
|
||||
bio, err := getDBpediaComment(input.Name)
|
||||
if err == nil && bio != "" {
|
||||
pdk.Log(pdk.LogDebug, "Found DBpedia comment")
|
||||
output := BiographyOutput{Biography: bio}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("DBpedia comment failed: %v", err))
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("Biography not found for: %s (%s)", input.Name, input.MBID))
|
||||
pdk.SetErrorString("biography not found")
|
||||
return 1
|
||||
}
|
||||
|
||||
//go:wasmexport nd_get_artist_images
|
||||
func ndGetArtistImages() int32 {
|
||||
var input ArtistInput
|
||||
if err := pdk.InputJSON(&input); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogDebug, fmt.Sprintf("GetArtistImages: name=%s, mbid=%s", input.Name, input.MBID))
|
||||
|
||||
var q string
|
||||
if input.MBID != "" {
|
||||
q = fmt.Sprintf(`SELECT ?img WHERE { ?artist wdt:P434 "%s"; wdt:P18 ?img } LIMIT 1`, input.MBID)
|
||||
} else if input.Name != "" {
|
||||
escapedName := strings.ReplaceAll(input.Name, "\"", "\\\"")
|
||||
q = fmt.Sprintf(`SELECT ?img WHERE { ?artist rdfs:label "%s"@en; wdt:P18 ?img } LIMIT 1`, escapedName)
|
||||
} else {
|
||||
pdk.SetErrorString("MBID or Name required for Wikidata Image lookup")
|
||||
return 1
|
||||
}
|
||||
|
||||
result, err := sparqlQuery(wikidataEndpoint, q)
|
||||
if err != nil {
|
||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, input.MBID))
|
||||
pdk.SetErrorString("image not found")
|
||||
return 1
|
||||
}
|
||||
if result.Results.Bindings[0].Img != nil {
|
||||
output := ImagesOutput{
|
||||
Images: []ImageInfo{{URL: result.Results.Bindings[0].Img.Value, Size: 0}},
|
||||
}
|
||||
if err := pdk.OutputJSON(output); err != nil {
|
||||
pdk.SetError(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
pdk.Log(pdk.LogInfo, fmt.Sprintf("Image not found for: %s (%s)", input.Name, input.MBID))
|
||||
pdk.SetErrorString("image not found")
|
||||
return 1
|
||||
}
|
||||
|
||||
func main() {}
|
||||
Reference in New Issue
Block a user