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