jonnybarnes.uk/app/Jobs/SaveProfileImage.php
Jonny Barnes df9b0dc490
Move webmention parsing and authorship into the app
The jonnybarnes/webmentions-parser package is now archived, and its
guzzlehttp/guzzle ^6|^7 constraint was the only thing holding Guzzle
back from 8. Port the parts we use into App\Services\WebMention and
drop the package.

Parser keeps getMentionType and the checkInReplyTo/LikeOf/RepostOf
checks unchanged. Authorship follows the same authorship algorithm,
with some fixes:

- fetch the author page with Laravel's Http client, so tests can fake
  it, and treat connection failures as AuthorshipParserException too
- keep state in locals so one instance can be reused safely
- make the url == uid == author-page check actually compare the
  h-card's uid, rather than an undefined variable

Add direct tests for both, and mark the library as archived on the
projects page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:23:49 +00:00

80 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Exceptions\AuthorshipParserException;
use App\Services\WebMention\Authorship;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
class SaveProfileImage implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
protected array $microformats
) {}
/**
* Execute the job.
*/
public function handle(Authorship $authorship): void
{
try {
$author = $authorship->findAuthor($this->microformats);
} catch (AuthorshipParserException) {
return;
}
$photo = Arr::get($author, 'properties.photo.0');
$home = Arr::get($author, 'properties.url.0');
if (is_array($photo) && array_key_exists('value', $photo)) {
$photo = $photo['value'];
}
if (is_array($home)) {
$home = array_shift($home);
}
// dont save pbs.twimg.com links
if (
$photo
&& parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com'
&& parse_url($photo, PHP_URL_HOST) !== 'twitter.com'
) {
try {
$response = Http::throw()->get($photo);
$image = $response->body();
} catch (ConnectionException|RequestException) {
// we are opening and reading the default image so that
$default = public_path().'/assets/profile-images/default-image';
$handle = fopen($default, 'rb');
$image = fread($handle, filesize($default));
fclose($handle);
}
$path = public_path().'/assets/profile-images/'.parse_url($home, PHP_URL_HOST).'/image';
$parts = explode('/', $path);
$name = array_pop($parts);
$dir = implode('/', $parts);
if (! is_dir($dir) && ! mkdir($dir, 0755, true) && ! is_dir($dir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir));
}
file_put_contents("$dir/$name", $image);
}
}
}