如何在聊天框中实现和飞书一样能自动识别链接内容😊😊😊

1,705 阅读3分钟

无论是在飞书上还是在企业微信上,当我们发送一个链接的时候,它都会自动转换为一个链接,那么接下来我们将会实现一个简单的 demo,将会实现如下功能:

  1. 普通链接展示网站的基本信息;
  2. YouTube 视频链接直接提供视频组件播放;

url 解析工具

首先我们需要编写一个 url 解析工具,来对我们传入的链接进行解析,如下代码所所示:

const isYoutubeLink = (url: string) => {
  const youtubeRegex =
    /(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
  return youtubeRegex.test(url);
};

const YoutubeEmbed = (props: any) => {
  const videoId = props.url.match(
    /(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
  )[1];
  const embedUrl = `https://www.youtube.com/embed/${videoId}`;
  return (
    <div className="youtube-embed">
      <iframe
        width="560"
        height="315"
        src={embedUrl}
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowFullScreen
        title="Embedded YouTube Video"
      />
    </div>
  );
};

在上面的代码中,isYoutubeLink 函数接收一个字符串 url,然后使用正则表达式检查该字符串是否为有效的 YouTube 视频链接。youtubeRegex 用于匹配典型的 YouTube 链接格式,支持多种 URL 格式(如 youtube.com/watch?v=, youtube.com/v/, youtube.com/embed/, youtu.be/ 等等)。

YoutubeEmbed:这个 React 组件接收一个包含 YouTube URL 的 props,提取出视频 ID,并生成嵌入 YouTube 视频的 iframe。props.url 是包含 YouTube 链接的字符串。使用正则表达式从 URL 中提取视频 ID。生成嵌入 YouTube 视频的 URL,并通过 iframe 显示视频。

视频的组件我们编写好了,接下来我们要编写普通 url 的了,如下代码所示:

import axios from "axios";
import * as cheerio from "cheerio";

const getMetaData = async (url: string) => {
  try {
    const { data } = await axios.get(url);
    const $ = cheerio.load(data);

    const metaData = {
      title:
        $('meta[property="og:title"]').attr("content") || $("title").text(),
      description:
        $('meta[property="og:description"]').attr("content") ||
        $('meta[name="description"]').attr("content"),
      image: $('meta[property="og:image"]').attr("content"),
      url: $('meta[property="og:url"]').attr("content") || url,
    };

    return metaData;
  } catch (error) {
    console.error("Error fetching meta data:", error);
    return null;
  }
};

export default getMetaData;

在上面的这些代码中,它实现了从指定的 URL 获取网页的元数据(如标题、描述、图片和 URL),并返回这些数据。

解决跨域

当我们在浏览器中对juejin.cn发起网络的请求的时候,会出现跨域的问题,那么我们需要一个代理服务器来帮我们解决这个问题,如下代码所示:

/** @type {import('next').NextConfig} */

const nextConfig = {
  async rewrites() {
    return [
      {
        source: "/api/:path*",
        destination: "https://juejin.cn/:path*", // 将所有 /api 的请求代理到 juejin.cn
      },
    ];
  },
  async headers() {
    return [
      {
        // 使所有的 CORS 请求可以正常工作
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Origin", value: "*" },
          {
            key: "Access-Control-Allow-Methods",
            value: "GET,HEAD,OPTIONS,POST,PUT",
          },
          {
            key: "Access-Control-Allow-Headers",
            value:
              "Origin, X-Requested-With, Content-Type, Accept, Authorization",
          },
        ],
      },
    ];
  },
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.alias["http-proxy-middleware"] =
        "http-proxy-middleware/dist/client";
    }
    return config;
  },
};

export default nextConfig;

这部分代码的作用是将所有以 /api 开头的请求代理到 https://juejin.cn。例如,请求 /api/path 将被重写为请求 https://juejin.cn/path。这在开发过程中非常有用,可以避免 CORS 问题,并且可以使 API 请求更简洁。

具体实现

首先先上完整代码,如下所示:

"use client";

import React, { useEffect, useState } from "react";
import getMetaData from "@/assets";

const url = "https://juejin.cn/";

const isYoutubeLink = (url: string) => {
  const youtubeRegex =
    /(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
  return youtubeRegex.test(url);
};

const YoutubeEmbed = (props: any) => {
  const videoId = props.url.match(
    /(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
  )[1];
  const embedUrl = `https://www.youtube.com/embed/${videoId}`;
  return (
    <div className="youtube-embed">
      <iframe
        width="560"
        height="315"
        src={embedUrl}
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowFullScreen
        title="Embedded YouTube Video"
      />
    </div>
  );
};

const Preview = () => {
  const [metaData, setMetaData] = useState<any>(null);

  useEffect(() => {
    const fetchData = async () => {
      const data = await getMetaData(`/api/?url=${encodeURIComponent(url)}`);
      setMetaData(data);
    };

    fetchData();
  }, []);

  if (!metaData) {
    return <div>Loading...</div>;
  }

  if (isYoutubeLink(url)) {
    return <YoutubeEmbed url={url} />;
  }

  return (
    <div className="link-preview">
      {metaData.image && (
        // eslint-disable-next-line @next/next/no-img-element
        <img
          src={metaData.image}
          alt={metaData.title}
          style={{ width: "100px", height: "100px" }}
        />
      )}
      <div>
        <h3>{metaData.title}</h3>
        <p>{metaData.description}</p>
      </div>
    </div>
  );
};

export default Preview;

在上面的代码中,我们是通过手动的方式传入了一个掘金的 url,它的结果如下图所示:

20240606084302

我们将 url 换成 YouTube 的链接,最终结果如下所示:

blogs.images20240606084407.png

这样就可以实现根据url的内容来识别出改内容,并且在聊天框中显示不同的内容。

总结

只要我们将这些样式优化一下,我们就基本可以实现和飞书或者企业微信一样的效果了,可以自动识别 url 内容。

通过这种方式将纯文本链接自动转换为可点击的超链接能够显著提升用户体验。用户无需复制链接并粘贴到浏览器地址栏,只需点击即可访问相应的网页。这种便捷性是现代聊天工具的重要特点之一。

如果是视频的话亦可以直接在线播放,而无需另外点开再去播放相关的视频。