1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
<?php
namespace Digitigrade;
class LinkPreview implements \JsonSerializable {
public string $url;
public string $siteName;
public string $title;
public string $description;
public string $style;
public ?string $imageUrl = null;
/**
* Generates a link preview for the given URL.
* @param string $url
* @return LinkPreview|null the link preview, or null if none is available
*/
public static function generate(string $url): ?self {
$p = new self();
$p->url = $url;
return $p->update() ? $p : null;
}
/**
* Updates the information for this link preview.
* @return bool true if successful
*/
public function update(): bool {
if (!(str_starts_with($this->url, "https://") || str_starts_with($this->url, "http://"))) {
return false;
}
// TODO: Respect Content-Type header from response, maybe set an Accept header in the request?
// I was able to find a URL serving SHIFT-JIS, but they do not specify a charset in the Content-Type header.
// https://www2d.biglobe.ne.jp/~msyk/charcode/cp932/Windows-31J-charset.html
$max_length = 256 * 1024; // 256KiB should be enough for the document head
$c = @file_get_contents($this->url, false, null, 0, $max_length);
if (!$c)
return false;
$d = \Dom\HTMLDocument::createFromString($c, LIBXML_NOERROR);
$url = $this->url;
$siteName = hostname_from_uri($this->url);
$style = 'text';
// opengraph
$title = $d->querySelector("meta[property='og:title']")?->getAttribute('content');
$description = $d->querySelector("meta[property='og:description']")?->getAttribute('content');
$url = $d->querySelector("meta[property='og:url']")?->getAttribute('content');
$siteName = $d->querySelector("meta[property='og:site_name']")?->getAttribute('content');
$imageUrl = $d->querySelector("meta[property='og:image']")?->getAttribute('content');
// fallbacks
$title = $title ?? $d->querySelector('title')?->textContent;
$description = $description ?? $d->querySelector('meta[name=description]')?->getAttribute('content');
if ($d->querySelector("meta[name='twitter:card'][content=summary_large_image]") != null) {
$style = 'largeImage';
}
// turn text into smallImage if an image is present
$style = ($style == 'text' && isset($imageUrl)) ? 'smallImage' : $style;
if (!isset($title, $description))
return false;
$this->url = $url;
$this->siteName = $siteName;
$this->title = $title;
$this->description = $description;
$this->style = $style;
$this->imageUrl = $imageUrl ?? null;
return true;
}
public function jsonSerialize(): array {
return [
'url' => $this->url,
'siteName' => $this->siteName,
'title' => $this->title,
'description' => $this->description,
'style' => $this->style,
'imageUrl' => $this->imageUrl
];
}
}
|