More work on parsing microformats
This commit is contained in:
parent
4179ee10e9
commit
2735abde64
@ -54,42 +54,72 @@ class Endpoint {
|
||||
|
||||
if ($response->getStatusCode() < 400)
|
||||
{
|
||||
[$type, $content] = $this->parseContent($response->getContent(), $target);
|
||||
$author = $this->parseAuthor($response->getContent());
|
||||
$document = new Crawler($response->getContent());
|
||||
|
||||
$webmention = new Webmention(null, $target, $source, $type, $content, $author);
|
||||
if (!$this->hasMention($target, $document))
|
||||
{
|
||||
throw new TargetNotMentionedException();
|
||||
}
|
||||
|
||||
$container = $this->getContainer($target, $document);
|
||||
$type = $this->parseMentionType($target, $container);
|
||||
$content = $this->parseContent($target, $container, $type);
|
||||
$author = $this->parseAuthor($container);
|
||||
|
||||
$webmention = new Webmention(null, $target, $source, $type, null, $author);
|
||||
$this->gateway->save($webmention);
|
||||
} else {
|
||||
throw new SourceNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function parseContent(string $content, string $target) : array
|
||||
private function hasMention(string $target, Crawler $document) : bool
|
||||
{
|
||||
$body = new Crawler($content);
|
||||
$anchors = $body->filter('a[href="'. $target . '"]');
|
||||
|
||||
if (!$anchors->count()) {
|
||||
throw new TargetNotMentionedException();
|
||||
}
|
||||
$type = $this->classToMentionType($anchors->attr('class'));
|
||||
return [$type, null];
|
||||
return $document->filter('a[href="' . $target . '"]')->count() > 0;
|
||||
}
|
||||
|
||||
private function classToMentionType(string $class = "") : MentionType
|
||||
private function getContainer(string $target, Crawler $document) : Crawler
|
||||
{
|
||||
return $document->filter('a[href="' . $target . '"]')->closest('.h-entry') ?? $document;
|
||||
}
|
||||
|
||||
private function parseMentionType(string $target, Crawler $document) : MentionType
|
||||
{
|
||||
$class = $document->filter('a[href="'. $target . '"]')->attr('class');
|
||||
|
||||
if (str_contains($class, "u-like-of")) {
|
||||
return MentionType::Like;
|
||||
} else if (str_contains($class, "u-in-reply-to")) {
|
||||
return MentionType::Reply;
|
||||
} else if (str_contains($class, "u-repost-of")) {
|
||||
return MentionType::Repost;
|
||||
}
|
||||
|
||||
return MentionType::Mention;
|
||||
}
|
||||
|
||||
private function parseAuthor(string $author) : ?string
|
||||
private function parseContent(string $target, Crawler $document, MentionType $type) : ?string
|
||||
{
|
||||
return match ($type) {
|
||||
MentionType::Like => "Liked this post",
|
||||
MentionType::Reply => $document->innerText(),
|
||||
MentionType::Repost => "Reposted this post",
|
||||
MentionType::Mention => $document->closest('a[href="' . $target . '"]')->innerText()
|
||||
};
|
||||
}
|
||||
|
||||
private function parseAuthor(Crawler $document) : ?string
|
||||
{
|
||||
$card = $document->filter('.p-author.h-card')->eq(0);
|
||||
|
||||
if ($card)
|
||||
{
|
||||
$name = $card->filter('.p-name')?->text();
|
||||
$url = $card->filter('.u-url')?->text();
|
||||
$photo = $card->filter('.u-photo')?->attr('src');
|
||||
|
||||
return implode(", ", [$name, $url, $photo]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -4,26 +4,28 @@ namespace Lewisdale\Webmentions\Models;
|
||||
|
||||
enum MentionType {
|
||||
case Like;
|
||||
case Comment;
|
||||
case Reply;
|
||||
case Mention;
|
||||
case Repost;
|
||||
|
||||
public function toString() : string
|
||||
{
|
||||
return match ($this) {
|
||||
MentionType::Like => "like",
|
||||
MentionType::Reply => "reply",
|
||||
MentionType::Mention => "mention"
|
||||
MentionType::Mention => "mention",
|
||||
MentionType::Repost => "repost",
|
||||
};
|
||||
}
|
||||
|
||||
public static function from(string $string) : MentionType
|
||||
{
|
||||
switch($string) {
|
||||
case "like": return MentionType::Like;
|
||||
case "reply": return MentionType::Reply;
|
||||
default: return MentionType::Mention;
|
||||
}
|
||||
return match($string) {
|
||||
"like" => MentionType::Like,
|
||||
"reply" => MentionType::Reply,
|
||||
"repost" => MentionType::Repost,
|
||||
default => MentionType::Mention
|
||||
};
|
||||
}
|
||||
}
|
||||
?>
|
@ -14,6 +14,10 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class EndpointTest extends TestCase {
|
||||
private function objectContains(string $key, mixed $value) {
|
||||
return $this->callback(fn(object $obj) => $obj->$key === $value);
|
||||
}
|
||||
|
||||
#[TestWith(["https://my.url.com", true])]
|
||||
#[TestWith(["my.url.com", false])]
|
||||
public function testValidatesUrls(string $url, bool $expected)
|
||||
@ -139,16 +143,7 @@ class EndpointTest extends TestCase {
|
||||
|
||||
$mockGateway->expects($this->once())
|
||||
->method('save')
|
||||
->with($this->equalTo(
|
||||
new Webmention(
|
||||
null,
|
||||
$target,
|
||||
$source,
|
||||
MentionType::Like,
|
||||
null,
|
||||
null
|
||||
))
|
||||
);
|
||||
->with($this->objectContains('type', MentionType::Like));
|
||||
|
||||
$endpoint = new Endpoint($mockClient, $mockGateway);
|
||||
|
||||
@ -189,16 +184,7 @@ class EndpointTest extends TestCase {
|
||||
|
||||
$mockGateway->expects($this->once())
|
||||
->method('save')
|
||||
->with($this->equalTo(
|
||||
new Webmention(
|
||||
null,
|
||||
$target,
|
||||
$source,
|
||||
MentionType::Mention,
|
||||
null,
|
||||
null
|
||||
))
|
||||
);
|
||||
->with($this->objectContains('type', MentionType::Mention));
|
||||
|
||||
$endpoint = new Endpoint($mockClient, $mockGateway);
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
@ -238,16 +224,47 @@ class EndpointTest extends TestCase {
|
||||
|
||||
$mockGateway->expects($this->once())
|
||||
->method('save')
|
||||
->with($this->equalTo(
|
||||
new Webmention(
|
||||
null,
|
||||
$target,
|
||||
$source,
|
||||
MentionType::Reply,
|
||||
null,
|
||||
null
|
||||
))
|
||||
);
|
||||
->with($this->objectContains('type', MentionType::Reply));
|
||||
|
||||
$endpoint = new Endpoint($mockClient, $mockGateway);
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
}
|
||||
|
||||
public function testItShouldParseAWebmentionAsARepost()
|
||||
{
|
||||
$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-repost-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->objectContains('type', MentionType::Repost));
|
||||
|
||||
$endpoint = new Endpoint($mockClient, $mockGateway);
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
|
@ -78,7 +78,7 @@ class SqliteGatewayTest extends TestCase
|
||||
null,
|
||||
"https://lewisdale.dev/post/a-new-post",
|
||||
"https://a-source.url",
|
||||
MentionType::Comment,
|
||||
MentionType::Reply,
|
||||
"No content",
|
||||
"Some Author Name"
|
||||
));
|
||||
@ -124,7 +124,7 @@ class SqliteGatewayTest extends TestCase
|
||||
null,
|
||||
"https://lewisdale.dev/post/a-new-post",
|
||||
"https://a-source.url",
|
||||
MentionType::Comment,
|
||||
MentionType::Reply,
|
||||
"Some content",
|
||||
"Some Author Name"
|
||||
));
|
||||
|
Loading…
Reference in New Issue
Block a user