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