巧用ChatGPT改造代码 让PIX主题片刻支持外部链接转化为卡片(二)

本文更新于2024/10/18,如无重大BUG,本文不再更新。

这是让我最伤脑子的改造,网络上每个站点的安全配置不同,网速不同,让我不能完全照顾到所有网站。并且对于大型网站由于安全性太高,如某宝、某猫一直获取不到标题和描述。刚开始最大的难点在于获得外链的站标,因为现在好些网站的图片都使用了CDN并且加入了防盗链等设置,虽然能获取到站标的准确地址,但就是在卡片上显示不了。不管如何优化,始终都不满意。正准备放弃之时,忽然看到还有专门解析站标的网站。本篇经过挑选,最终选择Favicon.im作为实现站标的解析网站,并经过代码改造,终于算是一个相对完美的版本。

强烈建议提前备份pix/inc/pix-type.php文件。鱼和熊掌不可兼得,以为引入外部网址,考虑到速度,代码里融合了redis+memcached的缓存机制,这两个都是PHP的扩展,如果对自己的主机没有权限安装,以下代码可能无法运行,你也可以将相关代码删除后试试。

本文是以下文章的代码升级版本:

巧用ChatGPT改造代码 让PIX主题片刻同时支持本地链接和外部链接转化为卡片 巧用ChatGPT改造代码 让PIX主题片刻同时支持本地链接和外部链接转化为卡片 本代码已经升级: [pix_post ids=371] 改造代码最开始的目的是看到其他hexo的网站上有“分享好物”的功能,可以推广但不限于淘宝、京东商品,我的是wordpress网站,搜索没有相关的... 时间:2023/10/27 分类:技术相关

升级及优化内容:

1、在获取站标和标题的基础上增加了获取描述。

巧用ChatGPT改造代码 让PIX主题片刻支持外部链接转化为卡片(二)-似水流年
巧用ChatGPT改造代码 让PIX主题片刻支持外部链接转化为卡片(二)-似水流年

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]

改造完毕。

历史上本站今天曾发布的文章:

Comments | 7 条评论
  • obaby

    Google Chrome 126 Google Chrome 126 Mac OS X 10.15 Mac OS X 10.15 1中国–山东–青岛 联通 ip address 112.224.*.*

    最近折腾的东西又不少啊。

    • 似水流年

      Microsoft Edge 129 Microsoft Edge 129 Windows 10 Windows 10 1中国–河南–漯河 联通 ip address 115.49.*.*

      还得折腾,有重大BUG,提交后不显示了,后台编辑也丢失了。😂

  • 网友小宋

    Microsoft Edge 129 Microsoft Edge 129 Windows 10 Windows 10 1中国 中国联通 ip address 2408:8221:6010:1cf1:*:*

    不知道上服务器远的原因,我感觉每次访问都不快。😃

    • 似水流年

      IBrowse r IBrowse r Android 12 Android 12 1中国–河南–漯河 联通 ip address 115.49.*.*

      家里用的联通,我感觉网络还行,你是移动联通还是电信?

      • 网友小宋

        Microsoft Edge 129 Microsoft Edge 129 Windows 10 Windows 10 1中国–河南–漯河 联通 ip address 115.48.*.*

        联通啊!和你的一样。

        • 似水流年

          IBrowse r IBrowse r Android 12 Android 12 1中国–河南–漯河 联通 ip address 182.126.*.*

          那应该是在国外的原因。

  • 奶爸网

    Microsoft Edge 119 Microsoft Edge 119 Windows 10 Windows 10 1中国–上海–上海 电信 ip address 180.159.*.*

    试试

消息盒子
# 您需要首次评论以获取消息 #
# 您需要首次评论以获取消息 #

只显示最新10条未读和已读信息