mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2024-12-23 21:00:24 +03:00
425 lines
15 KiB
PHP
425 lines
15 KiB
PHP
<?php
|
|
|
|
class BandcampBridge extends BridgeAbstract
|
|
{
|
|
const MAINTAINER = 'sebsauvage, Roliga';
|
|
const NAME = 'Bandcamp Bridge';
|
|
const URI = 'https://bandcamp.com/';
|
|
const CACHE_TIMEOUT = 600; // 10min
|
|
const DESCRIPTION = 'New bandcamp releases by tag, band or album';
|
|
const PARAMETERS = [
|
|
'By tag' => [
|
|
'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'] = "<img src='$small_img' /><br/>$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'] = "<img src='$small_img' /><br/>$full_title<br/>";
|
|
if ($tralbum_data->type === 'a') {
|
|
$item['content'] .= '<ol>';
|
|
foreach ($tralbum_data->tracks as $track) {
|
|
$item['content'] .= "<li>$track->title</li>";
|
|
}
|
|
$item['content'] .= '</ol>';
|
|
}
|
|
if (!empty($tralbum_data->about)) {
|
|
$item['content'] .= '<p>'
|
|
. nl2br($tralbum_data->about)
|
|
. '</p>';
|
|
}
|
|
|
|
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
|
|
$response = getContents($url);
|
|
$data = json_decode($response);
|
|
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;
|
|
}
|
|
}
|