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; } // 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 $ch = curl_init($this->url); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: text/html,application/xhtml+xml,application/xml', ]); curl_setopt($ch, CURLOPT_RANGE, "0-256000"); // request first 256KB of the page curl_setopt($ch, CURLOPT_MAXFILESIZE, 8 * 1024 * 1024); // don't download more than 8MiB of HTML curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $response_charset = null; curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$response_charset) { $len = strlen($header); $header = explode(':', $header, 2); if (count($header) != 2) return $len; // content-type: text/html; charset=utf-8 if (strtolower(trim($header[0])) == 'content-type') { $vals = explode(';', $header[1]); foreach($vals as $val) { $val = strtolower(trim($val)); if(str_starts_with($val,"charset=")) { $response_charset = substr($val, 8); } } } return $len; }); $c = curl_exec($ch); if (!$c) return false; $d = \Dom\HTMLDocument::createFromString($c, LIBXML_NOERROR, $response_charset); $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 $url = $url ?? $this->url; $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 ]; } }