Parse likes and reply types using microformats

This commit is contained in:
Lewis Dale 2023-03-14 09:12:53 +00:00
parent 9d9e465a37
commit 0c316d18a2
2 changed files with 174 additions and 11 deletions

View File

@ -16,10 +16,10 @@ use Symfony\Component\DomCrawler\Crawler;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
class Endpoint { class Endpoint {
private readonly WebmentionGatewayInterface $gateway;
function __construct( function __construct(
private readonly HttpClientInterface $httpClient private readonly HttpClientInterface $httpClient,
private readonly WebmentionGatewayInterface $gateway
) )
{} {}
@ -54,17 +54,18 @@ class Endpoint {
if ($response->getStatusCode() < 400) if ($response->getStatusCode() < 400)
{ {
$content = $this->parseContent($response->getContent(), $target); [$type, $content] = $this->parseContent($response->getContent(), $target);
$author = $this->parseAuthor($response->getContent()); $author = $this->parseAuthor($response->getContent());
$webmention = new Webmention(null, $target, $source, MentionType::from(""), $content, $author); $webmention = new Webmention(null, $target, $source, $type, $content, $author);
$this->gateway->save($webmention); $this->gateway->save($webmention);
} else { } else {
throw new SourceNotFoundException(); throw new SourceNotFoundException();
} }
} }
private function parseContent(string $content, string $target) : ?string
private function parseContent(string $content, string $target) : array
{ {
$body = new Crawler($content); $body = new Crawler($content);
$anchors = $body->filter('a[href="'. $target . '"]'); $anchors = $body->filter('a[href="'. $target . '"]');
@ -72,8 +73,19 @@ class Endpoint {
if (!$anchors->count()) { if (!$anchors->count()) {
throw new TargetNotMentionedException(); throw new TargetNotMentionedException();
} }
$type = $this->classToMentionType($anchors->attr('class'));
return [$type, null];
}
return null; private function classToMentionType(string $class = "") : MentionType
{
if (str_contains($class, "u-like-of")) {
return MentionType::Like;
} else if (str_contains($class, "u-reply-to")) {
return MentionType::Reply;
}
return MentionType::Mention;
} }
private function parseAuthor(string $author) : ?string private function parseAuthor(string $author) : ?string

View File

@ -5,6 +5,9 @@ use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
use Lewisdale\Webmentions\Exceptions\InvalidUrlException; use Lewisdale\Webmentions\Exceptions\InvalidUrlException;
use Lewisdale\Webmentions\Exceptions\SourceNotFoundException; use Lewisdale\Webmentions\Exceptions\SourceNotFoundException;
use Lewisdale\Webmentions\Exceptions\TargetNotMentionedException; use Lewisdale\Webmentions\Exceptions\TargetNotMentionedException;
use Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
use Lewisdale\Webmentions\Models\MentionType;
use Lewisdale\Webmentions\Models\Webmention;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\Attributes\TestWith;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
@ -15,7 +18,7 @@ class EndpointTest extends TestCase {
#[TestWith(["my.url.com", false])] #[TestWith(["my.url.com", false])]
public function testValidatesUrls(string $url, bool $expected) public function testValidatesUrls(string $url, bool $expected)
{ {
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class)); $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
$this->assertEquals($expected, $endpoint->validateUrl($url), "Expected $url"); $this->assertEquals($expected, $endpoint->validateUrl($url), "Expected $url");
} }
@ -25,14 +28,14 @@ class EndpointTest extends TestCase {
public function testThrowsInvalidUrlExceptionIfTheUrlIsInvalid(string $source, string $target) public function testThrowsInvalidUrlExceptionIfTheUrlIsInvalid(string $source, string $target)
{ {
$this->expectException(InvalidUrlException::class); $this->expectException(InvalidUrlException::class);
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class)); $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
$endpoint->receiveWebmention($source, $target); $endpoint->receiveWebmention($source, $target);
} }
public function testThrowsInvalidTargetExceptionIfTheTargetUrlIsNotOnCurrentSite() public function testThrowsInvalidTargetExceptionIfTheTargetUrlIsNotOnCurrentSite()
{ {
$this->expectException(InvalidTargetException::class); $this->expectException(InvalidTargetException::class);
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class)); $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
$endpoint->receiveWebmention("https://a-valid-source.url", "https://not-my-site.com"); $endpoint->receiveWebmention("https://a-valid-source.url", "https://not-my-site.com");
} }
@ -61,7 +64,7 @@ class EndpointTest extends TestCase {
$this->expectException(SourceNotFoundException::class); $this->expectException(SourceNotFoundException::class);
$endpoint = new Endpoint($mockClient); $endpoint = new Endpoint($mockClient, $this->createMock(WebmentionGatewayInterface::class));
$endpoint->receiveWebmention($source, $target); $endpoint->receiveWebmention($source, $target);
} }
@ -98,7 +101,155 @@ class EndpointTest extends TestCase {
$this->expectException(TargetNotMentionedException::class); $this->expectException(TargetNotMentionedException::class);
$endpoint = new Endpoint($mockClient); $endpoint = new Endpoint($mockClient, $this->createMock(WebmentionGatewayInterface::class));
$endpoint->receiveWebmention($source, $target);
}
public function testItShouldParseAWebmentionAsALikeIfItHasTheCorrectMicroFormat()
{
$source = "https://my-valid-source-url.com";
$target = "https://lewisdale.dev/post/a-post-page";
$content = <<<XML
<html>
<head>
</head>
<body>
<h1>Some content</h1>
<p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
<p>I'm liking <a href="$target" class="u-like-of">this post</a>.</p>
</body>
</html>
XML;
$mockClient = $this->createMock(HttpClientInterface::class);
$mockResponse = $this->createMock(ResponseInterface::class);
$mockGateway = $this->createMock(WebmentionGatewayInterface::class);
$mockClient->expects($this->once())
->method('request')
->with($this->identicalTo('GET'), $this->identicalTo($source))
->will($this->returnValue($mockResponse));
$mockResponse->method('getStatusCode')
->will($this->returnValue(200));
$mockResponse->method('getContent')
->willReturn($content);
$mockGateway->expects($this->once())
->method('save')
->with($this->equalTo(
new Webmention(
null,
$target,
$source,
MentionType::Like,
null,
null
))
);
$endpoint = new Endpoint($mockClient, $mockGateway);
$endpoint->receiveWebmention($source, $target);
}
public function testItShouldParseAWebmentionAsAMentionIfItHasNoMicroformat()
{
$source = "https://my-valid-source-url.com";
$target = "https://lewisdale.dev/post/a-post-page";
$content = <<<XML
<html>
<head>
</head>
<body>
<h1>Some content</h1>
<p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
<p>I'm writing about <a href="$target" class="my-link">this post</a>.</p>
</body>
</html>
XML;
$mockClient = $this->createMock(HttpClientInterface::class);
$mockResponse = $this->createMock(ResponseInterface::class);
$mockGateway = $this->createMock(WebmentionGatewayInterface::class);
$mockClient->expects($this->once())
->method('request')
->with($this->identicalTo('GET'), $this->identicalTo($source))
->will($this->returnValue($mockResponse));
$mockResponse->method('getStatusCode')
->will($this->returnValue(200));
$mockResponse->method('getContent')
->willReturn($content);
$mockGateway->expects($this->once())
->method('save')
->with($this->equalTo(
new Webmention(
null,
$target,
$source,
MentionType::Mention,
null,
null
))
);
$endpoint = new Endpoint($mockClient, $mockGateway);
$endpoint->receiveWebmention($source, $target);
}
public function testItShouldParseAWebmentionAsAReplyIfItHasTheCorrectMicroFormat()
{
$source = "https://my-valid-source-url.com";
$target = "https://lewisdale.dev/post/a-post-page";
$content = <<<XML
<html>
<head>
</head>
<body>
<h1>Some content</h1>
<p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
<p>I'm writing about <a href="$target" class="u-reply-to">this post</a>.</p>
</body>
</html>
XML;
$mockClient = $this->createMock(HttpClientInterface::class);
$mockResponse = $this->createMock(ResponseInterface::class);
$mockGateway = $this->createMock(WebmentionGatewayInterface::class);
$mockClient->expects($this->once())
->method('request')
->with($this->identicalTo('GET'), $this->identicalTo($source))
->will($this->returnValue($mockResponse));
$mockResponse->method('getStatusCode')
->will($this->returnValue(200));
$mockResponse->method('getContent')
->willReturn($content);
$mockGateway->expects($this->once())
->method('save')
->with($this->equalTo(
new Webmention(
null,
$target,
$source,
MentionType::Reply,
null,
null
))
);
$endpoint = new Endpoint($mockClient, $mockGateway);
$endpoint->receiveWebmention($source, $target); $endpoint->receiveWebmention($source, $target);
} }
} }