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
123 lines
4.1 KiB
PHP
123 lines
4.1 KiB
PHP
<?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 doesn’t 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 doesn’t so delete
|
||
$webmention->delete();
|
||
|
||
return;
|
||
} // note we don’t need to do anything if it still is a like
|
||
}
|
||
if ($webmention->type === 'repost-of') {
|
||
if ($parser->checkRepostOf($microformats, $this->note->uri) === false) {
|
||
// it doesn’t so delete
|
||
$webmention->delete();
|
||
|
||
return;
|
||
} // again, we don’t 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);
|
||
}
|
||
}
|