diff --git a/app/Http/Controllers/LikesController.php b/app/Http/Controllers/LikesController.php new file mode 100644 index 00000000..02922e86 --- /dev/null +++ b/app/Http/Controllers/LikesController.php @@ -0,0 +1,20 @@ +paginate(20); + + return view('likes.index', compact('likes')); + } + + public function show(Like $like) + { + return view('likes.show', compact('like')); + } +} diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index c6498a69..36cba4ab 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -6,8 +6,9 @@ use Storage; use Monolog\Logger; use Ramsey\Uuid\Uuid; use App\Jobs\ProcessImage; -use App\{Media, Note, Place}; +use App\Services\LikeService; use Monolog\Handler\StreamHandler; +use App\{Like, Media, Note, Place}; use Intervention\Image\ImageManager; use Illuminate\Http\{Request, Response}; use App\Exceptions\InvalidTokenException; @@ -73,6 +74,14 @@ class MicropubController extends Controller if (stristr($tokenData->getClaim('scope'), 'create') === false) { return $this->returnInsufficientScopeResponse(); } + if ($request->has('properties.like-of') || $request->has('like-of')) { + $like = (new LikeService())->createLike($request); + + return response()->json([ + 'response' => 'created', + 'location' => config('app.url') . "/likes/$like->id", + ], 201)->header('Location', config('app.url') . "/likes/$like->id"); + } $data = []; $data['client-id'] = $tokenData->getClaim('client_id'); if ($request->header('Content-Type') == 'application/json') { diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php index dbf5adc7..9801c8ea 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -22,14 +22,13 @@ class NotesController extends Controller return (new ActivityStreamsService)->siteOwnerResponse(); } - $notes = Note::orderBy('id', 'desc') + $notes = Note::latest() ->with('place', 'media', 'client') ->withCount(['webmentions As replies' => function ($query) { $query->where('type', 'in-reply-to'); }])->paginate(10); - $aslink = config('app.url'); - return view('notes.index', compact('notes', 'aslink')); + return view('notes.index', compact('notes')); } /** @@ -46,9 +45,7 @@ class NotesController extends Controller return (new ActivityStreamsService)->singleNoteResponse($note); } - $aslink = $note->longurl; - - return view('notes.show', compact('note', 'aslink')); + return view('notes.show', compact('note')); } /** diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b2fa4ba4..1f67d385 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -38,6 +38,7 @@ class Kernel extends HttpKernel \App\Http\Middleware\LinkHeadersMiddleware::class, //\App\Http\Middleware\DevTokenMiddleware::class, \App\Http\Middleware\LocalhostSessionMiddleware::class, + \App\Http\Middleware\ActivityStreamLinks::class, ], 'api' => [ diff --git a/app/Http/Middleware/ActivityStreamLinks.php b/app/Http/Middleware/ActivityStreamLinks.php new file mode 100644 index 00000000..4c240759 --- /dev/null +++ b/app/Http/Middleware/ActivityStreamLinks.php @@ -0,0 +1,28 @@ +path() === '/') { + $response->header('Link', '<' . config('app.url') . '>; rel="application/activity+json"', false); + } + if ($request->is('notes/*')) { + $response->header('Link', '<' . $request->url() . '>; rel="application/activity+json"', false); + } + + return $response; + } +} diff --git a/app/Jobs/ProcessLike.php b/app/Jobs/ProcessLike.php new file mode 100644 index 00000000..84fffa7d --- /dev/null +++ b/app/Jobs/ProcessLike.php @@ -0,0 +1,59 @@ +like = $like; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle(Client $client, Authorship $authorship) + { + $response = $client->request('GET', $this->like->url); + $mf2 = \Mf2\parse((string) $response->getBody(), $this->like->url); + if (array_has($mf2, 'items.0.properties.content')) { + $this->like->content = $mf2['items'][0]['properties']['content'][0]['html']; + } + + try { + $author = $authorship->findAuthor($mf2); + if (is_array($author)) { + $this->like->author_name = $author['name']; + $this->like->author_url = $author['url']; + } + if (is_string($author) && $author !== '') { + $this->like->author_name = $author; + } + } catch (AuthorshipParserException $exception) { + return; + } + + $this->like->save(); + } +} diff --git a/app/Jobs/SyndicateToFacebook.php b/app/Jobs/SyndicateToFacebook.php index c8826396..c117ae13 100644 --- a/app/Jobs/SyndicateToFacebook.php +++ b/app/Jobs/SyndicateToFacebook.php @@ -40,6 +40,7 @@ class SyndicateToFacebook implements ShouldQueue 'form_params' => [ 'source' => $this->note->longurl, 'target' => 'https://brid.gy/publish/facebook', + 'bridgy_omit_link' => 'maybe', ], ] ); diff --git a/app/Jobs/SyndicateToTwitter.php b/app/Jobs/SyndicateToTwitter.php index fb91dc4b..1d50f904 100644 --- a/app/Jobs/SyndicateToTwitter.php +++ b/app/Jobs/SyndicateToTwitter.php @@ -41,6 +41,7 @@ class SyndicateToTwitter implements ShouldQueue 'form_params' => [ 'source' => $this->note->longurl, 'target' => 'https://brid.gy/publish/twitter', + 'bridgy_omit_link' => 'maybe', ], ] ); diff --git a/app/Like.php b/app/Like.php new file mode 100644 index 00000000..aae728e6 --- /dev/null +++ b/app/Like.php @@ -0,0 +1,44 @@ +attributes['url'] = normalize_url($value); + } + + public function setAuthorUrlAttribute($value) + { + $this->attributes['author_url'] = normalize_url($value); + } + + public function getContentAttribute($value) + { + if ($value === null) { + return $this->url; + } + + $mf2 = Mf2\parse($value, $this->url); + + return $this->filterHTML($mf2['items'][0]['properties']['content'][0]['html']); + } + + public function filterHTML($html) + { + $config = HTMLPurifier_Config::createDefault(); + $config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier'); + $config->set('HTML.TargetBlank', true); + $purifier = new HTMLPurifier($config); + + return $purifier->purify($html); + } +} diff --git a/app/Services/LikeService.php b/app/Services/LikeService.php new file mode 100644 index 00000000..d4ff6f76 --- /dev/null +++ b/app/Services/LikeService.php @@ -0,0 +1,37 @@ +header('Content-Type') == 'application/json') { + //micropub request + $url = normalize_url($request->input('properties.like-of.0')); + } + if ( + ($request->header('Content-Type') == 'x-www-url-formencoded') + || + ($request->header('Content-Type') == 'multipart/form-data') + ) { + $url = normalize_url($request->input('like-of')); + } + + $like = Like::create(['url' => $url]); + ProcessLike::dispatch($like); + + return $like; + } +} diff --git a/changelog.md b/changelog.md index 56bd18b4..e1100688 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## Version 0.9 (2017-10-06) + - Add support for `likes` (issue#69) + - Only included links on truncated syndicated notes https://brid.gy/about#omit-link + +## Version 0.8.1 (2017-09-16) + - Order notes by latest (issue#70) + - AcitivtyStream support is now indicated with HTTP Link headers + ## Version 0.8 (2017-09-16) - Improve embedding of tweets (issue#66) - Allow for “responsive” images (issue#62) diff --git a/composer.lock b/composer.lock index 3f639063..e4d03966 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.36.7", + "version": "3.36.9", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "421088947540b1c7956cd693b032124e2c74eb76" + "reference": "7b89fa65cccb966da1599b715dcea8c09eafc175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/421088947540b1c7956cd693b032124e2c74eb76", - "reference": "421088947540b1c7956cd693b032124e2c74eb76", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7b89fa65cccb966da1599b715dcea8c09eafc175", + "reference": "7b89fa65cccb966da1599b715dcea8c09eafc175", "shasum": "" }, "require": { @@ -84,7 +84,7 @@ "s3", "sdk" ], - "time": "2017-09-13T18:56:17+00:00" + "time": "2017-09-15T19:12:04+00:00" }, { "name": "barnabywalters/mf-cleaner", @@ -2085,16 +2085,16 @@ }, { "name": "laravel/scout", - "version": "v3.0.9", + "version": "v3.0.10", "source": { "type": "git", "url": "https://github.com/laravel/scout.git", - "reference": "84762c8ed51cb57f09b5f465e09993e48baf9d55" + "reference": "681c15a26bbc973528af2e77e0bb61981dc07206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/scout/zipball/84762c8ed51cb57f09b5f465e09993e48baf9d55", - "reference": "84762c8ed51cb57f09b5f465e09993e48baf9d55", + "url": "https://api.github.com/repos/laravel/scout/zipball/681c15a26bbc973528af2e77e0bb61981dc07206", + "reference": "681c15a26bbc973528af2e77e0bb61981dc07206", "shasum": "" }, "require": { @@ -2146,7 +2146,7 @@ "laravel", "search" ], - "time": "2017-09-13T18:24:31+00:00" + "time": "2017-09-14T12:32:30+00:00" }, { "name": "laravel/tinker", diff --git a/database/factories/LikeFactory.php b/database/factories/LikeFactory.php new file mode 100644 index 00000000..ad3e9551 --- /dev/null +++ b/database/factories/LikeFactory.php @@ -0,0 +1,12 @@ +define(App\Like::class, function (Faker $faker) { + return [ + 'url' => $faker->url, + 'author_name' => $faker->name, + 'author_url' => $faker->url, + 'content' => '
+ {!! $like->content !!} ++
+ {!! $like->content !!} ++