Skip to content

在 PHP 中检测纯 Emoji 消息

简介

在构建聊天机器人或消息应用时,你经常需要检测用户是否发送了只包含 emoji 的消息。这在以下场景中很常见:

  • Instagram 故事反应 - 用户经常只回复 "❤️" 或 "😂"
  • 聊天反应 - 快速的 emoji 回复,如 "👍" 或 "🔥"
  • 消息过滤 - 将纯 emoji 消息与文本消息区别处理

在本文中,我们将使用正则表达式构建一个简单的 PHP 函数来检测纯 emoji 消息。

Unicode 的挑战

Emoji 比表面看起来要复杂得多。一个看起来是单个 emoji 的字符实际上可能是多个 Unicode 字符的组合:

  • 基础 emoji:单个码点,如 😀 (U+1F600)
  • 肤色修饰符:👋🏽 是 👋 + 🏽(中等肤色)
  • ZWJ 序列:👨‍👩‍👧 实际上是 👨 + ZWJ + 👩 + ZWJ + 👧
  • 国旗 emoji:🇨🇳 由两个区域指示符号组成

这种复杂性意味着我们不能简单地检查单个字符——我们需要一个覆盖所有 emoji Unicode 范围的正则表达式模式。

实现

这是一个处理大多数常见 emoji 的实用实现:

php
/**
 * 检查文本是否只包含 emoji 和空白字符。
 * 用于检测反应或纯 emoji 消息。
 */
function isOnlyEmojis(?string $text): bool
{
    if (!$text || trim($text) === '') {
        return false;
    }

    // 移除所有空白字符
    $textWithoutWhitespace = preg_replace('/\s+/u', '', $text);

    if ($textWithoutWhitespace === '') {
        return false;
    }

    // Emoji 的 Unicode 正则表达式模式
    $emojiPattern = '/^[\x{1F600}-\x{1F64F}' .  // 表情符号
        '\x{1F300}-\x{1F5FF}' .  // 杂项符号和象形文字
        '\x{1F680}-\x{1F6FF}' .  // 交通和地图
        '\x{1F700}-\x{1F77F}' .  // 炼金术符号
        '\x{1F780}-\x{1F7FF}' .  // 扩展几何形状
        '\x{1F800}-\x{1F8FF}' .  // 补充箭头-C
        '\x{1F900}-\x{1F9FF}' .  // 补充符号和象形文字
        '\x{1FA00}-\x{1FA6F}' .  // 国际象棋符号
        '\x{1FA70}-\x{1FAFF}' .  // 符号和象形文字扩展-A
        '\x{1F1E6}-\x{1F1FF}' .  // 国旗(区域指示符号)
        '\x{2600}-\x{26FF}' .    // 杂项符号(太阳、月亮、星星)
        '\x{2700}-\x{27BF}' .    // Dingbats
        '\x{2300}-\x{23FF}' .    // 杂项技术符号
        '\x{2B50}\x{2B55}' .     // 星星和圆圈
        '\x{231A}\x{231B}' .     // 手表和沙漏
        '\x{2328}' .             // 键盘
        '\x{23CF}' .             // 弹出符号
        '\x{23E9}-\x{23F3}' .    // 媒体控制符号
        '\x{23F8}-\x{23FA}' .    // 媒体控制符号
        '\x{24C2}' .             // 圆圈 M
        '\x{25AA}\x{25AB}' .     // 小方块
        '\x{25B6}\x{25C0}' .     // 播放按钮
        '\x{25FB}-\x{25FE}' .    // 方块
        '\x{2934}\x{2935}' .     // 箭头
        '\x{2B05}-\x{2B07}' .    // 箭头
        '\x{3030}' .             // 波浪线
        '\x{303D}' .             // 部分交替标记
        '\x{3297}' .             // 圆圈祝字
        '\x{3299}' .             // 圆圈秘字
        '\x{FE0F}' .             // 变体选择符-16(emoji 呈现)
        '\x{200D}' .             // 零宽连接符(用于组合 emoji)
        ']+$/u';

    return preg_match($emojiPattern, $textWithoutWhitespace) === 1;
}

使用示例

php
// 基础 emoji
isOnlyEmojis('😀');           // true
isOnlyEmojis('❤️🔥');         // true
isOnlyEmojis('👍👍👍');       // true

// 带空白字符(仍然有效)
isOnlyEmojis('😀 😂 🎉');     // true

// 组合 emoji(ZWJ 序列)
isOnlyEmojis('👨‍👩‍👧');         // true
isOnlyEmojis('👋🏽');          // true(肤色修饰符)

// 混合内容(不是纯 emoji)
isOnlyEmojis('你好 👋');      // false
isOnlyEmojis('太棒了! 🔥');   // false
isOnlyEmojis('123');          // false

// 边界情况
isOnlyEmojis('');             // false
isOnlyEmojis(null);           // false
isOnlyEmojis('   ');          // false(只有空白字符)

理解 Unicode 范围

让我们分解模式中的主要 Unicode 范围:

范围描述示例
1F600-1F64F表情符号😀 😂 😍 🙄
1F300-1F5FF杂项符号和象形文字🌟 🎉 🔥 💡
1F680-1F6FF交通和地图🚀 ✈️ 🏠
1F900-1F9FF补充符号🤖 🦄 🧠
1F1E6-1F1FF区域指示符🇺🇸 🇪🇸 🇨🇳
2600-26FF杂项符号☀️ ⭐ ♥️
FE0F变体选择符使文本符号显示为 emoji
200D零宽连接符连接 emoji(👨‍👩‍👧)

为什么要包含 FE0F 和 200D?

变体选择符-16(U+FE0F)告诉系统将字符渲染为 emoji。零宽连接符(U+200D)将多个 emoji 连接成一个组合 emoji,如家庭 emoji 或职业 emoji。

实际用例:聊天机器人反应

以下是如何在聊天机器人上下文中使用它:

php
function handleIncomingMessage(string $message): void
{
    if (isOnlyEmojis($message)) {
        // 这是一个反应!区别处理
        logReaction($message);
        // 对于只是 "👍" 可能不需要触发完整的 AI 响应
        return;
    }

    // 作为普通文本消息处理
    processTextMessage($message);
}

局限性

这种基于正则表达式的方法有一些局限性:

  1. 新 emoji:Unicode 定期添加新 emoji。该模式可能不包含最新的添加。
  2. 一些边界情况:某些很少使用的 emoji 组合可能不会被检测到。
  3. 性能:对于非常长的字符串,正则表达式可能比基于库的方法慢。

替代方案:使用库

对于更全面的 emoji 检测,考虑使用专用库如 p3k/emoji-detector

bash
composer require p3k/emoji-detector
php
use Emoji\Detector;

$detector = new Detector();
$result = $detector->detect('你好 👋 世界');

// 返回检测到的 emoji 数组及其位置

库方法更健壮,但会增加一个依赖。对于简单的"这是纯 emoji 吗?"检查,正则表达式方法轻量且足够。

结论

检测纯 emoji 消息对许多消息应用很有用。关键点是:

  1. Emoji 很复杂 - 它们可以是单个字符或组合
  2. 使用 Unicode 范围 - 在正则表达式中覆盖主要的 emoji 块
  3. 不要忘记 ZWJ 和 FE0F - 这些不可见字符对组合 emoji 至关重要
  4. 考虑你的用例 - 简单的正则表达式适用于大多数情况;对于高级需求使用库

这里展示的正则表达式方法处理了绝大多数实际 emoji 使用情况,同时保持代码无依赖。