jonnybarnes.uk/app/Jobs/ProcessWebMention.php
Jonny Barnes e08763c526
Migrate outbound HTTP calls from Guzzle to the Http facade
Replaces every raw GuzzleHttp\Client usage (Nominatim, web.archive.org,
webmention fetch/discover/send, Bridgy syndication, IndieAuth client_id
lookup, contact avatar/h-card fetch, profile image download, and the
CloudConvert screenshot pipeline) with Illuminate\Support\Facades\Http,
and enables Http::preventStrayRequests() globally in tests so any
un-faked outbound call now fails loudly instead of silently hitting the
network.

The CloudConvert retry-until-finished middleware in AppServiceProvider
is replaced by a plain polling loop in SaveScreenshot, which also fixes
a latent bug where the old middleware decoded a response object instead
of its body. Bridgy syndication jobs keep their tries=1/no-retry
semantics unchanged to avoid duplicate publishes. Guzzle's PSR-7 helpers
(Header, UriResolver, Utils) stay in SendWebMentions since Http has no
equivalent for them.

Every affected test file's Guzzle MockHandler/HandlerStack boilerplate
is replaced with Http::fake()/Http::sequence().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8j7cJhCiYDsGUgB7obKNQ
2026-07-26 09:12:01 +01:00

123 lines
4.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Exceptions\RemoteContentNotFoundException;
use App\Models\Note;
use App\Models\WebMention;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Jonnybarnes\WebmentionsParser\Exceptions\InvalidMentionException;
use Jonnybarnes\WebmentionsParser\Parser;
use Mf2;
class ProcessWebMention implements ShouldQueue
{
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
protected Note $note,
protected string $source
) {}
/**
* Execute the job.
*
* @throws RemoteContentNotFoundException
* @throws InvalidMentionException
*/
public function handle(Parser $parser): void
{
try {
$response = Http::get($this->source);
} catch (ConnectionException) {
throw new RemoteContentNotFoundException;
}
if ($response->failed()) {
throw new RemoteContentNotFoundException;
}
$this->saveRemoteContent($response->body(), $this->source);
$microformats = Mf2\parse($response->body(), $this->source);
$webmentions = WebMention::where('source', $this->source)->get();
foreach ($webmentions as $webmention) {
// check webmention still references target
// we try each type of mention (reply/like/repost)
if ($webmention->type === 'in-reply-to') {
if ($parser->checkInReplyTo($microformats, $this->note->uri) === false) {
// it doesnt so delete
$webmention->delete();
return;
}
// webmention is still a reply, so update content
dispatch(new SaveProfileImage($microformats));
$webmention->mf2 = json_encode($microformats);
$webmention->save();
return;
}
if ($webmention->type === 'like-of') {
if ($parser->checkLikeOf($microformats, $this->note->uri) === false) {
// it doesnt so delete
$webmention->delete();
return;
} // note we dont need to do anything if it still is a like
}
if ($webmention->type === 'repost-of') {
if ($parser->checkRepostOf($microformats, $this->note->uri) === false) {
// it doesnt so delete
$webmention->delete();
return;
} // again, we dont need to do anything if it still is a repost
}
}// foreach
// no webmention in the db so create new one
$webmention = new WebMention;
$type = $parser->getMentionType($microformats); // throw error here?
dispatch(new SaveProfileImage($microformats));
$webmention->source = $this->source;
$webmention->target = $this->note->uri;
$webmention->commentable_id = $this->note->id;
$webmention->commentable_type = Note::class;
$webmention->type = $type;
$webmention->mf2 = json_encode($microformats);
$webmention->save();
}
/**
* Save the HTML of a webmention for future use.
*/
private function saveRemoteContent(string $html, string $url): void
{
$filenameFromURL = str_replace(
['https://', 'http://'],
['https/', 'http/'],
$url
);
if (str_ends_with($url, '/')) {
$filenameFromURL .= 'index.html';
}
$path = storage_path().'/HTML/'.$filenameFromURL;
$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", $html);
}
}