[
'tag' => [
'name' => 'tag',
'type' => 'text',
'required' => true,
'exampleValue' => 'hip-hop-rap'
]
],
'By band' => [
'band' => [
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the band page URL',
'required' => true,
'exampleValue' => 'aesoprock'
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'changes'
],
'limit' => [
'name' => 'limit',
'type' => 'number',
'required' => true,
'title' => 'Number of releases to return',
'defaultValue' => 5
]
],
'By label' => [
'label' => [
'name' => 'label',
'type' => 'text',
'title' => 'label name as seen in the label page URL',
'required' => true
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'changes'
],
'limit' => [
'name' => 'limit',
'type' => 'number',
'title' => 'Number of releases to return',
'defaultValue' => 5
]
],
'By album' => [
'band' => [
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the album page URL',
'required' => true,
'exampleValue' => 'aesoprock'
],
'album' => [
'name' => 'album',
'type' => 'text',
'title' => 'Album name as seen in the album page URL',
'required' => true,
'exampleValue' => 'appleseed'
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'tracks'
]
]
];
const IMGURI = 'https://f4.bcbits.com/';
const IMGSIZE_300PX = 23;
const IMGSIZE_700PX = 16;
private $feedName;
public function getIcon()
{
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
public function collectData()
{
switch ($this->queriedContext) {
case 'By tag':
$url = self::URI . 'api/hub/1/dig_deeper';
$data = $this->buildRequestJson();
$header = [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
];
$opts = [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
];
$content = getContents($url, $header, $opts);
$json = json_decode($content);
if ($json->ok !== true) {
returnServerError('Invalid response');
}
foreach ($json->items as $entry) {
$url = $entry->tralbum_url;
$artist = $entry->artist;
$title = $entry->title;
// e.g. record label is the releaser, but not the artist
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
$full_title = $artist . ' - ' . $title;
$full_artist = $artist;
if (isset($releaser)) {
$full_title .= ' (' . $releaser . ')';
$full_artist .= ' (' . $releaser . ')';
}
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
$item = [
'uri' => $url,
'author' => $full_artist,
'title' => $full_title
];
$item['content'] = "
$full_title";
$item['enclosures'] = [$img];
$this->items[] = $item;
}
break;
case 'By band':
case 'By label':
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$regex = '/band_id=(\d+)/';
if (preg_match($regex, $html, $matches) == false) {
returnServerError('Unable to find band ID on: ' . $this->getURI());
}
$band_id = $matches[1];
$tralbums = [];
switch ($this->queriedContext) {
case 'By band':
case 'By label':
$query_data = [
'band_id' => $band_id
];
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
for ($i = 0; $i < $num_albums; $i++) {
$album_basic_data = $band_data->discography[$i];
// 'a' or 't' for albums and individual tracks respectively
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
$query_data = [
'band_id' => $band_id,
'tralbum_type' => $tralbum_type,
'tralbum_id' => $album_basic_data->item_id
];
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
}
break;
case 'By album':
$regex = '/album=(\d+)/';
if (preg_match($regex, $html, $matches) == false) {
returnServerError('Unable to find album ID on: ' . $this->getURI());
}
$album_id = $matches[1];
$query_data = [
'band_id' => $band_id,
'tralbum_type' => 'a',
'tralbum_id' => $album_id
];
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
break;
}
foreach ($tralbums as $tralbum_data) {
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
foreach ($tralbum_data->tracks as $track) {
$query_data = [
'band_id' => $band_id,
'tralbum_type' => 't',
'tralbum_id' => $track->track_id
];
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
$this->items[] = $this->buildTralbumItem($track_data);
}
} else {
$this->items[] = $this->buildTralbumItem($tralbum_data);
}
}
break;
}
}
private function buildTralbumItem($tralbum_data)
{
$band_data = $tralbum_data->band;
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
//
// If the album/track is released under a label/a band other than the artist
// themselves, append that releaser name to the title and artist/author.
//
// This sadly doesn't always work right for individual tracks as the artist
// of the track is always set to the releaser.
$artist = $tralbum_data->tralbum_artist;
$full_title = $artist . ' - ' . $tralbum_data->title;
$full_artist = $artist;
if (isset($tralbum_data->label)) {
$full_title .= ' (' . $tralbum_data->label . ')';
$full_artist .= ' (' . $tralbum_data->label . ')';
} elseif ($band_data->name !== $artist) {
$full_title .= ' (' . $band_data->name . ')';
$full_artist .= ' (' . $band_data->name . ')';
}
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
$item = [
'uri' => $tralbum_data->bandcamp_url,
'author' => $full_artist,
'title' => $full_title,
'enclosures' => [$img],
'timestamp' => $tralbum_data->release_date
];
$item['categories'] = [];
foreach ($tralbum_data->tags as $tag) {
$item['categories'][] = $tag->norm_name;
}
// Give articles a unique UID depending on its track list
// Releases should then show up as new articles when tracks are added
if ($this->getInput('type') === 'changes') {
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
foreach ($tralbum_data->tracks as $track) {
$item['uid'] .= $track->track_id;
}
}
$item['content'] = "
$full_title
";
if ($tralbum_data->type === 'a') {
$item['content'] .= '
' . nl2br($tralbum_data->about) . '
'; } return $item; } private function buildRequestJson() { $requestJson = [ 'tag' => $this->getInput('tag'), 'page' => 1, 'sort' => 'date' ]; return json_encode($requestJson); } private function getImageUrl($id, $size) { return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg'; } private function apiGet($endpoint, $query_data) { $url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data); // todo: 429 Too Many Requests happens a lot $data = json_decode(getContents($url)); return $data; } public function getURI() { switch ($this->queriedContext) { case 'By tag': if (!is_null($this->getInput('tag'))) { return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date'; } break; case 'By label': if (!is_null($this->getInput('label'))) { return 'https://' . $this->getInput('label') . '.bandcamp.com/music'; } break; case 'By band': if (!is_null($this->getInput('band'))) { return 'https://' . $this->getInput('band') . '.bandcamp.com/music'; } break; case 'By album': if (!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) { return 'https://' . $this->getInput('band') . '.bandcamp.com/album/' . $this->getInput('album'); } break; } return parent::getURI(); } public function getName() { switch ($this->queriedContext) { case 'By tag': if (!is_null($this->getInput('tag'))) { return $this->getInput('tag') . ' - Bandcamp Tag'; } break; case 'By band': if (isset($this->feedName)) { return $this->feedName . ' - Bandcamp Band'; } elseif (!is_null($this->getInput('band'))) { return $this->getInput('band') . ' - Bandcamp Band'; } break; case 'By label': if (isset($this->feedName)) { return $this->feedName . ' - Bandcamp Label'; } elseif (!is_null($this->getInput('label'))) { return $this->getInput('label') . ' - Bandcamp Label'; } break; case 'By album': if (isset($this->feedName)) { return $this->feedName . ' - Bandcamp Album'; } elseif (!is_null($this->getInput('album'))) { return $this->getInput('album') . ' - Bandcamp Album'; } break; } return parent::getName(); } public function detectParameters($url) { $params = []; // By tag $regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/'; if (preg_match($regex, $url, $matches) > 0) { $params['context'] = 'By tag'; $params['tag'] = urldecode($matches[2]); return $params; } // By band $regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/'; if (preg_match($regex, $url, $matches) > 0) { $params['context'] = 'By band'; $params['band'] = urldecode($matches[2]); return $params; } // By album $regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/'; if (preg_match($regex, $url, $matches) > 0) { $params['context'] = 'By album'; $params['band'] = urldecode($matches[2]); $params['album'] = urldecode($matches[3]); return $params; } return null; } }