巧用ChatGPT改造代码 让PIX主题片刻支持外部链接转化为卡片(二)
本文是《技术相关(共39篇)》目录的第 31 篇。阅读本文前,建议先阅读本文前3篇文章:
本文更新于2024/10/18,如无重大BUG,本文不再更新。
这是让我最伤脑子的改造,网络上每个站点的安全配置不同,网速不同,让我不能完全照顾到所有网站。并且对于大型网站由于安全性太高,如某宝、某猫一直获取不到标题和描述。刚开始最大的难点在于获得外链的站标,因为现在好些网站的图片都使用了CDN并且加入了防盗链等设置,虽然能获取到站标的准确地址,但就是在卡片上显示不了。不管如何优化,始终都不满意。正准备放弃之时,忽然看到还有专门解析站标的网站。本篇经过挑选,最终选择Favicon.im作为实现站标的解析网站,并经过代码改造,终于算是一个相对完美的版本。
强烈建议提前备份pix/inc/pix-type.php文件。鱼和熊掌不可兼得,以为引入外部网址,考虑到速度,代码里融合了redis+memcached的缓存机制,这两个都是PHP的扩展,如果对自己的主机没有权限安装,以下代码可能无法运行,你也可以将相关代码删除后试试。
本文是以下文章的代码升级版本:
升级及优化内容:
1、在获取站标和标题的基础上增加了获取描述。
2、优化了片刻显示速度。
3、支持了更多的普通网站转为成卡片。
代码如下:
[reply]
// 获取卡片信息
class CardFetcher
{
private static $instance;
private $redis;
private $memcached;
private function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->memcached = new Memcached();
$this->memcached->addServer('127.0.0.1', 11211);
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
// 获取缓存的 logo
public function getCachedLogo($url)
{
$redisKey = 'logo_'. md5($url);
$logoFromRedis = $this->redis->hGet('logos', $redisKey);
if ($logoFromRedis) {
return $logoFromRedis;
}
$logoFromMemcached = $this->memcached->get($redisKey);
if ($logoFromMemcached) {
return $logoFromMemcached;
}
return null;
}
// 缓存 logo
public function cacheLogo($url, $logo)
{
$redisKey = 'logo_'. md5($url);
$this->redis->hSet('logos', $redisKey, $logo);
$this->redis->expire('logos', 86400);
$this->memcached->set($redisKey, $logo);
}
// 获取缓存的元数据
public function getCachedMetadata($url)
{
$redisKey = 'metadata_'. md5($url);
$metadataFromRedis = $this->redis->hGet('metadatas', $redisKey);
if ($metadataFromRedis) {
return json_decode($metadataFromRedis, true);
}
$metadataFromMemcached = $this->memcached->get($redisKey);
if ($metadataFromMemcached) {
return json_decode($metadataFromMemcached, true);
}
return null;
}
// 缓存元数据
public function cacheMetadata($url, $metadata)
{
$redisKey = 'metadata_'. md5($url);
$this->redis->hSet('metadatas', $redisKey, json_encode($metadata));
$this->redis->expire('metadatas', 86400);
$this->memcached->set($redisKey, json_encode($metadata));
}
}
function extractMetaFromHtml($html)
{
$title = '';
$description = '';
// 使用简单的字符串查找来提取标题,避免正则表达式的开销
$startTitle = strpos($html, '<title>');
if ($startTitle!== false) {
$endTitle = strpos($html, '</title>', $startTitle);
if ($endTitle!== false) {
$title = trim(substr($html, $startTitle + 7, $endTitle - $startTitle - 7));
}
}
if (empty($title)) {
$startH1 = strpos($html, '<h1>');
if ($startH1!== false) {
$endH1 = strpos($html, '</h1>', $startH1);
if ($endH1!== false) {
$title = trim(substr($html, $startH1 + 4, $endH1 - $startH1 - 4));
}
}
}
if (empty($title)) {
$startOgTitle = strpos($html, '<meta property="og:title" content="');
if ($startOgTitle!== false) {
$endOgTitle = strpos($html, '"', $startOgTitle + 32);
if ($endOgTitle!== false) {
$title = trim(substr($html, $startOgTitle + 32, $endOgTitle - $startOgTitle - 32));
}
}
}
// 使用简单的字符串查找来提取描述
$startDescription = strpos($html, '<meta name="description" content="');
if ($startDescription!== false) {
$endDescription = strpos($html, '"', $startDescription + 34);
if ($endDescription!== false) {
$description = trim(substr($html, $startDescription + 34, $endDescription - $startDescription - 34));
}
}
if (empty($description)) {
$startOgDescription = strpos($html, '<meta property="og:description" content="');
if ($startOgDescription!== false) {
$endOgDescription = strpos($html, '"', $startOgDescription + 37);
if ($endOgDescription!== false) {
$description = trim(substr($html, $startOgDescription + 37, $endOgDescription - $startOgDescription - 37));
}
}
}
if (empty($description)) {
$bodyStart = strpos($html, '<body>');
$bodyEnd = strpos($html, '</body>');
if ($bodyStart!== false && $bodyEnd!== false) {
$bodyContent = substr($html, $bodyStart + 6, $bodyEnd - $bodyStart - 6);
$words = preg_split('/\s+/', strip_tags($bodyContent));
$description = implode(' ', array_slice($words, 0, 30));
}
}
return ['title' => $title, 'description' => $description, 'image' => null];
}
function get_external_link_metadata_with_cache($url)
{
// 设置主流常见的 USERAGENT
$userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
$cacheKey = 'external_link_metadata_'. md5($url);
$cachedMeta = CardFetcher::getInstance()->getCachedMetadata($cacheKey);
if ($cachedMeta) {
return $cachedMeta;
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_USERAGENT, $userAgent);
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 设置超时时间为 5 秒
$html = curl_exec($ch);
if ($html === false) {
error_log("Curl error for URL $url: ". curl_error($ch));
// 如果获取外部链接内容失败,尝试从缓存中获取默认值或者返回空值
return ['title' => '', 'description' => '', 'image' => null];
}
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($responseCode === 200 &&!empty($html)) {
$meta = extractMetaFromHtml($html);
CardFetcher::getInstance()->cacheMetadata($cacheKey, $meta);
return $meta;
} else {
return ['title' => '', 'description' => '', 'image' => null];
}
}
function get_external_link_logo($url)
{
$parsedUrl = parse_url($url);
if (!$parsedUrl ||!isset($parsedUrl['host'])) {
return null;
}
$domain = $parsedUrl['host'];
return "https://favicon.im/{$domain}";
}
function get_card_by_url($data)
{
$parsedUrl = parse_url($data);
$pid = null;
if (filter_var($data, FILTER_VALIDATE_URL)) {
if ($parsedUrl['host'] == $_SERVER['HTTP_HOST']) {
if ($post = get_page_by_path($parsedUrl['path'])) {
$pid = $post->ID;
}
} else {
$pid = $data;
}
} else {
$pid = intval($data);
}
if ($pid) {
$meta = [];
if (filter_var($pid, FILTER_VALIDATE_URL)) {
$meta['url'] = $pid;
$externalMeta = get_external_link_metadata_with_cache($pid);
$meta['title'] = $externalMeta['title'];
$meta['description'] = $externalMeta['description'];
$meta['image'] = get_external_link_logo($pid);
} else {
$type = get_post_type($pid);
$meta['title'] = get_the_title($pid);
$meta['url'] = get_permalink($pid);
$meta['description'] = get_the_excerpt($pid);
$metaImageUrl = get_the_post_thumbnail_url($pid, 'medium');
if ($metaImageUrl) {
$meta['image'] = $metaImageUrl;
} else {
$meta['image'] = get_external_link_logo($meta['url']);
}
if ($type == 'moment') {
$meta['title'] = '片刻';
$momentImg = THEME_URL. '/img/banner.jpg';
$lists = get_post_meta($pid, 'moment_ga', true);
if (is_array($lists) &&!empty($lists)) {
$momentImg = $lists[0]['thum'];
}
$meta['image'] = $momentImg;
}
}
$meta['pid'] = $pid;
return $meta;
} else {
return false;
}
}
// 卡片编辑
function card_type_box()
{
$output = '<div class="add_card_box">
<div class="edit_card_box">
<div class="tips"># 输入本站网址或外部网址,自动生成链接卡片</div>
<div class="tips">
<span>本站网址支持文章、片刻、页面内链及外链网址</span><br>
<span>淘宝示例:https://item.taobao.com/item.htm?id=******<br>京东示例:https://item.jd.com/******.html<br>拼多多示例:https://mobile.yangkeduo.com/goods1.html?goods_id=******</span></div>
<div class="edit_content">
<input type="text" placeholder="本站网址或外部网址" name="moment_card_link" id="moment_card_link" required="required">
<a class="push_card">生成卡片</a>
</div>
</div>
<div class="show_card"><div class="card_sortble" uk-sortable="handle:.moment_card_item"></div></div>
</div>';
echo $output;
exit();
}
add_action('wp_ajax_nopriv_card_type_box', 'card_type_box');
add_action('wp_ajax_card_type_box', 'card_type_box');
function get_moment_card()
{
try {
$cardUrl = esc_url($_POST['card_url']);
$arr = get_card_by_url($cardUrl);
$html = '';
if (is_array($arr)) {
$html = '<div class="moment_card_item" pid="'. $arr['pid']. '">
<a>
<div class="left"><img src="'. $arr['image']. '"></div>
<div class="right"><h4>'. $arr['title']. '</h4><div class="content">'. $arr['description']. '</div></div>
<span class="de_card"><i class="ri-close-line"></i></span>
</a>
</div>';
$res = ['html' => $html, 'state' => '1'];
} else {
$html = '未知错误';
$res = ['html' => $html, 'state' => '0'];
}
echo json_encode($res);
exit();
} catch (Exception $e) {
$html = '未知错误';
$res = ['html' => $html, 'state' => '0'];
echo json_encode($res);
exit();
}
}
add_action('wp_ajax_nopriv_get_moment_card', 'get_moment_card');
add_action('wp_ajax_get_moment_card', 'get_moment_card');
function get_m_card()
{
try {
global $post;
$pid = $post->ID;
$lists = get_post_meta($pid, 'moment_card', true);
$html = '';
if (is_array($lists) &&!empty($lists)) {
foreach ($lists as $index => $list) {
$cardInfo = get_card_by_url($list);
if ($cardInfo) {
$html.= '<div class="moment_card_item loop_card_item" pid="'. $cardInfo['pid']. '">
<a href="'. $cardInfo['url']. '" target="_blank">
<div class="left"><img src="'. $cardInfo['image']. '"></div>
<div class="right"><h4>'. $cardInfo['title']. '</h4><div class="content">'. $cardInfo['description']. '</div></div>
</a>
</div>';
}
}
return '<div class="card_list">
<div class="list_inner">'. $html. '</div>
</div>';
}
} catch (Exception $e) {
return false;
}
}
[/reply]
改造完毕。
您已阅读完《技术相关(共39篇)》目录的第 31 篇。请继续阅读本文后3篇文章:
obaby
最近折腾的东西又不少啊。
似水流年
还得折腾,有重大BUG,提交后不显示了,后台编辑也丢失了。😂
网友小宋
不知道上服务器远的原因,我感觉每次访问都不快。😃
似水流年
家里用的联通,我感觉网络还行,你是移动联通还是电信?
网友小宋
联通啊!和你的一样。
似水流年
那应该是在国外的原因。
奶爸网
试试