Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SoundcloudBridge] Add support for albums, reposts & likes #2236

Merged
merged 6 commits into from
Sep 20, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 114 additions & 39 deletions bridges/SoundcloudBridge.php
Original file line number Diff line number Diff line change
@@ -1,64 +1,83 @@
<?php
class SoundCloudBridge extends BridgeAbstract {

const MAINTAINER = 'kranack, Roliga';
const NAME = 'Soundcloud Bridge';
const URI = 'https://soundcloud.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns 10 newest music from user profile';

const PARAMETERS = array( array(
const PARAMETERS = array(array(
'u' => array(
'name' => 'username',
'required' => true
),
't' => array(
'name' => 'type',
'name' => 'Content',
'type' => 'list',
'defaultValue' => 'tracks',
'values' => array(
'All (except likes)' => 'all',
'Tracks' => 'tracks',
'Playlists' => 'playlists'
'Albums' => 'albums',
'Playlists' => 'playlists',
'Reposts' => 'reposts',
'Likes' => 'likes'
)
)
));

private $apiUrl = 'https://api-v2.soundcloud.com/';
// Without url=http, player URL returns a 404
private $playerUrl = 'https://w.soundcloud.com/player/?url=http';
private $widgetUrl = 'https://widget.sndcdn.com/';

private $feedTitle = null;
private $feedIcon = null;
private $clientIDCache = null;

private $clientIdRegex = '/client_id.*?"(.+?)"/';
private $widgetRegex = '/widget-.+?\.js/';

public function collectData(){
$res = $this->apiGet('resolve', array(
'url' => 'https://soundcloud.com/' . $this->getInput('u')
)) or returnServerError('No results for this query');
public function collectData() {
$res = $this->getUser($this->getInput('u'))
or returnServerError('No results for this query');

$this->feedTitle = $res->username;
$this->feedIcon = $res->avatar_url;

$tracks = $this->apiGet(
'users/' . urlencode($res->id) . '/' . $this->getInput('t'),
array('limit' => 31)
) or returnServerError('No results for this user/playlist');
$apiItems = $this->getUserItems($res->id, $this->getInput('t'))
or returnServerError('No results for ' . $this->getInput('t'));

$hasTrackObject = array('all', 'reposts', 'likes');

foreach ($apiItems->collection as $index => $apiItem) {
if (in_array($this->getInput('t'), $hasTrackObject) === true) {
$apiItem = $apiItem->track;
}

foreach ($tracks->collection as $index => $track) {
$item = array();
$item['author'] = $track->user->username;
$item['title'] = $track->user->username . ' - ' . $track->title;
$item['timestamp'] = strtotime($track->created_at);
$item['content'] = nl2br($track->description);
$item['enclosures'][] = $track->artwork_url;

$item['id'] = self::URI
. urlencode($this->getInput('u'))
. '/'
. urlencode($track->permalink);
$item['uri'] = self::URI
. urlencode($this->getInput('u'))
. '/'
. urlencode($track->permalink);
$item['author'] = $apiItem->user->username;
$item['title'] = $apiItem->user->username . ' - ' . $apiItem->title;
$item['timestamp'] = strtotime($apiItem->created_at);

$description = nl2br($apiItem->description);

$item['content'] = <<<HTML
<p>{$description}</p>
HTML;

if (isset($apiItem->tracks) && $apiItem->track_count > 0) {
$list = $this->getTrackList($apiItem->tracks);

$item['content'] .= <<<HTML
<p><strong>Tracks ({$apiItem->track_count})</strong></p>
{$list}
HTML;
}

$item['enclosures'][] = $apiItem->artwork_url;
$item['id'] = $apiItem->permalink_url;
$item['uri'] = $apiItem->permalink_url;
$this->items[] = $item;

if (count($this->items) >= 10) {
Expand All @@ -75,13 +94,17 @@ public function getIcon(){
return parent::getIcon();
}

public function getURI(){
return 'https://soundcloud.com/' . $this->getInput('u');
public function getURI() {
if ($this->getInput('u')) {
return self::URI . $this->getInput('u') . '/' . $this->getInput('t');
}

return parent::getURI();
}

public function getName(){
public function getName() {
if($this->feedTitle) {
return $this->feedTitle . ' - ' . self::NAME;
return $this->feedTitle . ' - ' . ucfirst($this->getInput('t')) . ' - ' . self::NAME;
}

return parent::getName();
Expand Down Expand Up @@ -113,8 +136,7 @@ private function getClientID(){
private function refreshClientID(){
$this->initClientIDCache();

// Without url=http, this returns a 404
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
$playerHTML = getContents($this->playerUrl)
or returnServerError('Unable to get player page.');

// Extract widget JS filenames from player page
Expand All @@ -125,7 +147,7 @@ private function refreshClientID(){

// Loop widget js files and extract client ID
foreach ($matches[0] as $widgetFile) {
$widgetURL = 'https://widget.sndcdn.com/' . $widgetFile;
$widgetURL = $this->widgetUrl . $widgetFile;

$widgetJS = getContents($widgetURL)
or returnServerError('Unable to get widget JS page.');
Expand All @@ -143,22 +165,75 @@ private function refreshClientID(){
}
}

private function buildAPIURL($endpoint, $parameters){
return 'https://api-v2.soundcloud.com/'
private function buildApiUrl($endpoint, $parameters) {
return $this->apiUrl
. $endpoint
. '?'
. http_build_query($parameters);
}

private function apiGet($endpoint, $parameters = array()) {
private function getUser($username) {
$parameters = array('url' => self::URI . $username);

return $this->getApi('resolve', $parameters);
}

private function getUserItems($userId, $type) {
$parameters = array('limit' => 10);
$endpoint = 'users/' . $userId . '/' . $type;

if ($type === 'playlists') {
$endpoint = 'users/' . $userId . '/playlists_without_albums';
}

if ($type === 'all') {
$endpoint = 'stream/users/' . $userId;
}

if ($type === 'reposts') {
$endpoint = 'stream/users/' . $userId . '/' . $type;
}

return $this->getApi($endpoint, $parameters);
}

private function getApi($endpoint, $parameters) {
$parameters['client_id'] = $this->getClientID();
$url = $this->buildApiUrl($endpoint, $parameters);

try {
return json_decode(getContents($this->buildAPIURL($endpoint, $parameters)));
return json_decode(getContents($url));
} catch (Exception $e) {
// Retry once with refreshed client ID
$parameters['client_id'] = $this->refreshClientID();
return json_decode(getContents($this->buildAPIURL($endpoint, $parameters)));
$url = $this->buildApiUrl($endpoint, $parameters);

return json_decode(getContents($url));
}
}

private function getTrackList($tracks) {
$trackids = '';

foreach ($tracks as $track) {
$trackids .= $track->id . ',';
}

$apiItems = $this->getApi(
'tracks', array('ids' => $trackids)
);

$list = '';
foreach($apiItems as $track) {
$list .= <<<HTML
<li>{$track->user->username} — <a href="{$track->permalink_url}">{$track->title}</a></li>
HTML;
}

$html = <<<HTML
<ul>{$list}</ul>
HTML;

return $html;
}
}