mirror of
https://github.com/koel/koel
synced 2024-11-27 22:40:26 +00:00
Like/unlike now work with Last.fm
This commit is contained in:
parent
7c4745fe4a
commit
8495452762
10 changed files with 197 additions and 15 deletions
52
app/Events/SongLikeToggled.php
Normal file
52
app/Events/SongLikeToggled.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Interaction;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
|
||||
class SongLikeToggled extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The ineraction (like/unlike) in action.
|
||||
*
|
||||
* @var Interaction
|
||||
*/
|
||||
public $interaction;
|
||||
|
||||
/**
|
||||
* The user who carries the action.
|
||||
*
|
||||
* @var User
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param Interaction $interaction
|
||||
* @param User $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Interaction $interaction, User $user = null)
|
||||
{
|
||||
$this->interaction = $interaction;
|
||||
$this->user = $user ?: auth()->user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should be broadcast on.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
48
app/Listeners/LoveTrackOnLastfm.php
Normal file
48
app/Listeners/LoveTrackOnLastfm.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\SongLikeToggled;
|
||||
use App\Services\Lastfm;
|
||||
|
||||
class LoveTrackOnLastfm
|
||||
{
|
||||
/**
|
||||
* The Last.fm service instance, which is DI'ed into our listener.
|
||||
*
|
||||
* @var Lastfm
|
||||
*/
|
||||
protected $lastfm;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param Lastfm $lastfm
|
||||
*/
|
||||
public function __construct(Lastfm $lastfm)
|
||||
{
|
||||
$this->lastfm = $lastfm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param SongLikeToggled $event
|
||||
*/
|
||||
public function handle(SongLikeToggled $event)
|
||||
{
|
||||
if (!$this->lastfm->enabled() ||
|
||||
!($sessionKey = $event->user->getLastfmSessionKey()) ||
|
||||
$event->interaction->song->album->artist->isUnknown()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->lastfm->toggleLoveTrack(
|
||||
$event->interaction->song->title,
|
||||
$event->interaction->song->album->artist->name,
|
||||
$sessionKey,
|
||||
$event->interaction->liked
|
||||
);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,11 @@ class Artist extends Model
|
|||
return $this->hasMany(Album::class);
|
||||
}
|
||||
|
||||
public function isUnknown()
|
||||
{
|
||||
return $this->id === self::UNKNOWN_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes the tags extracted from getID3 are HTML entity encoded.
|
||||
* This makes sure they are always sane.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\SongLikeToggled;
|
||||
use App\Traits\CanFilterByUser;
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
@ -80,6 +81,8 @@ class Interaction extends Model
|
|||
$interaction->liked = !$interaction->liked;
|
||||
$interaction->save();
|
||||
|
||||
event(new SongLikeToggled($interaction));
|
||||
|
||||
return $interaction;
|
||||
}
|
||||
|
||||
|
@ -108,6 +111,8 @@ class Interaction extends Model
|
|||
$interaction->liked = true;
|
||||
$interaction->save();
|
||||
|
||||
event(new SongLikeToggled($interaction));
|
||||
|
||||
$result[] = $interaction;
|
||||
}
|
||||
|
||||
|
@ -124,9 +129,11 @@ class Interaction extends Model
|
|||
*/
|
||||
public static function batchUnlike(array $songIds, User $user)
|
||||
{
|
||||
return DB::table('interactions')
|
||||
->whereIn('song_id', $songIds)
|
||||
->whereUserId($user->id)
|
||||
->update(['liked' => false]);
|
||||
foreach(self::whereIn('song_id', $songIds)->whereUserId($user->id)->get() as $interaction) {
|
||||
$interaction->liked = false;
|
||||
$interaction->save();
|
||||
|
||||
event(new SongLikeToggled($interaction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,14 +48,12 @@ class Song extends Model
|
|||
public function scrobble($timestamp)
|
||||
{
|
||||
// Don't scrobble the unknown guys. No one knows them.
|
||||
if ($this->album->artist->id === Artist::UNKNOWN_ID) {
|
||||
if ($this->album->artist->isUnknown()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auth()->user()->setHidden([]);
|
||||
|
||||
// If the current user hasn't connected to Last.fm, don't do shit.
|
||||
if (!$sessionKey = auth()->user()->getPreference('lastfm_session_key')) {
|
||||
if (!$sessionKey = auth()->user()->getLastfmSessionKey()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -112,6 +112,26 @@ AuthenticatableContract,
|
|||
$this->update(compact('preferences'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is connected to Last.fm.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function connectedToLastfm()
|
||||
{
|
||||
return !!$this->getLastfmSessionKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's Last.fm session key.
|
||||
*
|
||||
* @return string|null The key if found, or null if user isn't connected to Last.fm
|
||||
*/
|
||||
public function getLastfmSessionKey()
|
||||
{
|
||||
return $this->getPreference('lastfm_session_key');
|
||||
}
|
||||
|
||||
/**
|
||||
* User preferences are stored as a serialized associative array.
|
||||
*
|
||||
|
|
|
@ -16,8 +16,8 @@ class EventServiceProvider extends ServiceProvider
|
|||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
'App\Events\SomeEvent' => [
|
||||
'App\Listeners\EventListener',
|
||||
'App\Events\SongLikeToggled' => [
|
||||
'App\Listeners\LoveTrackOnLastfm',
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
@ -190,15 +190,14 @@ class Lastfm extends RESTfulService
|
|||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function scrobble($artist, $track, $timestamp, $album = null, $sk = null)
|
||||
public function scrobble($artist, $track, $timestamp, $album, $sk)
|
||||
{
|
||||
$params = compact('artist', 'track', 'timestamp');
|
||||
$params = compact('artist', 'track', 'timestamp', 'sk');
|
||||
|
||||
if ($album) {
|
||||
$params['album'] = $album;
|
||||
}
|
||||
|
||||
$params['sk'] = $sk ?: auth()->user()->getPreference('lastfm_session_key');
|
||||
$params['method'] = 'track.scrobble';
|
||||
|
||||
try {
|
||||
|
@ -210,6 +209,30 @@ class Lastfm extends RESTfulService
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Love or unlove a track on Last.fm.
|
||||
*
|
||||
* @param string $track The track name
|
||||
* @param string $artist The artist's name
|
||||
* @param string $sk The session key
|
||||
* @param boolean $love Whether to love or unlove. Such cheesy terms... urrgggh
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function toggleLoveTrack($track, $artist, $sk, $love = true)
|
||||
{
|
||||
$params = compact('track', 'artist', 'sk');
|
||||
$params['method'] = $love ? 'track.love' : 'track.unlove';
|
||||
|
||||
try {
|
||||
return (bool) $this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the parameters to use for _authenticated_ Last.fm API calls.
|
||||
* Such calls require:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Events\SongLikeToggled;
|
||||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
@ -44,6 +45,8 @@ class InteractionTest extends TestCase
|
|||
|
||||
public function testLikeRegister()
|
||||
{
|
||||
$this->expectsEvents(SongLikeToggled::class);
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$song = Song::orderBy('id')->first();
|
||||
|
@ -69,6 +72,8 @@ class InteractionTest extends TestCase
|
|||
|
||||
public function testBatchLikeAndUnlike()
|
||||
{
|
||||
$this->expectsEvents(SongLikeToggled::class);
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$songs = Song::orderBy('id')->take(2)->get();
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Events\SongLikeToggled;
|
||||
use App\Http\Controllers\API\InteractionController;
|
||||
use App\Http\Controllers\API\LastfmController;
|
||||
use App\Listeners\LoveTrackOnLastfm;
|
||||
use App\Models\Interaction;
|
||||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use App\Services\Lastfm;
|
||||
use GuzzleHttp\Client;
|
||||
|
@ -142,13 +147,32 @@ class LastfmTest extends TestCase
|
|||
|
||||
(new LastfmController($guard))->callback($request, $lastfm);
|
||||
|
||||
$this->assertEquals('bar', $user->getPreference('lastfm_session_key'));
|
||||
$this->assertEquals('bar', $user->getLastfmSessionKey());
|
||||
}
|
||||
|
||||
public function testControllerDisconnect()
|
||||
{
|
||||
$user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]);
|
||||
$this->actingAs($user)->delete('api/lastfm/disconnect');
|
||||
$this->assertNull($user->getPreference('lastfm_session_key'));
|
||||
$this->assertNull($user->getLastfmSessionKey());
|
||||
}
|
||||
|
||||
public function testLoveTrack()
|
||||
{
|
||||
$this->withoutEvents();
|
||||
$this->createSampleMediaSet();
|
||||
|
||||
$user = factory(User::class)->create(['preferences' => ['lastfm_session_key' => 'bar']]);
|
||||
|
||||
$interaction = Interaction::create([
|
||||
'user_id' => $user->id,
|
||||
'song_id' => Song::first()->id,
|
||||
]);
|
||||
|
||||
$lastfm = m::mock(Lastfm::class, ['enabled' => true]);
|
||||
$lastfm->shouldReceive('toggleLoveTrack')
|
||||
->withArgs([$interaction->song->title, $interaction->song->album->artist->name, 'bar', false]);
|
||||
|
||||
(new LoveTrackOnLastfm($lastfm))->handle(new SongLikeToggled($interaction, $user));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue