diff --git a/bridges/BandcampBridge.php b/bridges/BandcampBridge.php index 6c75ed5e..fa071465 100644 --- a/bridges/BandcampBridge.php +++ b/bridges/BandcampBridge.php @@ -1,73 +1,262 @@ array( - 'name' => 'tag', - 'type' => 'text', - 'required' => true + const DESCRIPTION = 'New bandcamp releases by tag, band or album'; + const PARAMETERS = array( + 'By tag' => array( + 'tag' => array( + 'name' => 'tag', + 'type' => 'text', + 'required' => true + ) + ), + 'By band' => array( + 'band' => array( + 'name' => 'band', + 'type' => 'text', + 'title' => 'Band name as seen in the band page URL', + 'required' => true + ), + 'type' => array( + 'name' => 'Articles are', + 'type' => 'list', + 'values' => array( + 'Releases' => 'releases', + 'Releases, new one when track list changes' => 'changes', + 'Individual tracks' => 'tracks' + ), + 'defaultValue' => 'changes' + ), + 'limit' => array( + 'name' => 'limit', + 'type' => 'number', + 'title' => 'Number of releases to return', + 'defaultValue' => 5 + ) + ), + 'By album' => array( + 'band' => array( + 'name' => 'band', + 'type' => 'text', + 'title' => 'Band name as seen in the album page URL', + 'required' => true + ), + 'album' => array( + 'name' => 'album', + 'type' => 'text', + 'title' => 'Album name as seen in the album page URL', + 'required' => true + ), + 'type' => array( + 'name' => 'Articles are', + 'type' => 'list', + 'values' => array( + '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(){ - $url = self::URI . 'api/hub/1/dig_deeper'; - $data = $this->buildRequestJson(); - $header = array( - 'Content-Type: application/json', - 'Content-Length: ' . strlen($data) - ); - $opts = array( - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POSTFIELDS => $data - ); - $content = getContents($url, $header, $opts) - or returnServerError('Could not complete request to: ' . $url); - - $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 = array( - 'uri' => $url, - 'author' => $full_artist, - 'title' => $full_title + switch($this->queriedContext) { + case 'By tag': + $url = self::URI . 'api/hub/1/dig_deeper'; + $data = $this->buildRequestJson(); + $header = array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen($data) ); - $item['content'] = "
$full_title"; - $item['enclosures'] = array($img); - $this->items[] = $item; + $opts = array( + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POSTFIELDS => $data + ); + $content = getContents($url, $header, $opts) + or returnServerError('Could not complete request to: ' . $url); + + $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 = array( + 'uri' => $url, + 'author' => $full_artist, + 'title' => $full_title + ); + $item['content'] = "
$full_title"; + $item['enclosures'] = array($img); + $this->items[] = $item; + } + break; + case 'By band': + case 'By album': + $html = getSimpleHTMLDOMCached($this->getURI(), 86400); + + $titleElement = $html->find('head meta[name=title]', 0) + or returnServerError('Unable to find title on: ' . $this->getURI()); + $this->feedName = $titleElement->content; + + $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 = array(); + switch($this->queriedContext) { + case 'By band': + $query_data = array( + '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 = array( + '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 = array( + '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 = array( + '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 = array( + 'uri' => $tralbum_data->bandcamp_url, + 'author' => $full_artist, + 'title' => $full_title, + 'enclosures' => array($img), + 'timestamp' => $tralbum_data->release_date + ); + + $item['categories'] = array(); + 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'] .= '
    '; + foreach ($tralbum_data->tracks as $track) { + $item['content'] .= "
  1. $track->title
  2. "; + } + $item['content'] .= '
'; + } + if (!empty($tralbum_data->about)) { + $item['content'] .= '

' + . nl2br($tralbum_data->about) + . '

'; + } + + return $item; + } + private function buildRequestJson(){ $requestJson = array( 'tag' => $this->getInput('tag'), @@ -81,11 +270,94 @@ class BandcampBridge extends BridgeAbstract { return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg'; } + private function apiGet($endpoint, $query_data) { + $url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data); + $data = json_decode(getContents($url)) + or returnServerError('API request to "' . $url . '" failed.'); + 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 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(){ - if(!is_null($this->getInput('tag'))) { - return $this->getInput('tag') . ' - Bandcamp Tag'; + 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 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 = array(); + + // By tag + $regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/'; + if(preg_match($regex, $url, $matches) > 0) { + $params['tag'] = urldecode($matches[2]); + return $params; + } + + // By band + $regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/'; + if(preg_match($regex, $url, $matches) > 0) { + $params['band'] = urldecode($matches[2]); + return $params; + } + + // By album + $regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/'; + if(preg_match($regex, $url, $matches) > 0) { + $params['band'] = urldecode($matches[2]); + $params['album'] = urldecode($matches[3]); + return $params; + } + + return null; + } }