Like/unlike now work with Last.fm

This commit is contained in:
An Phan 2015-12-21 21:49:00 +08:00
parent 7c4745fe4a
commit 8495452762
10 changed files with 197 additions and 15 deletions

View 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 [];
}
}

View 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
);
}
}

View file

@ -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.

View file

@ -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));
}
}
}

View file

@ -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;
}

View file

@ -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.
*

View file

@ -16,8 +16,8 @@ class EventServiceProvider extends ServiceProvider
* @var array
*/
protected $listen = [
'App\Events\SomeEvent' => [
'App\Listeners\EventListener',
'App\Events\SongLikeToggled' => [
'App\Listeners\LoveTrackOnLastfm',
],
];

View file

@ -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:

View file

@ -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();

View file

@ -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));
}
}