其中文章 《你知道9月1日新实施的〈人工智能生成合成内容标识办法〉吗?》 中使用的标识插件就是这个。

示范内容

比如下面文字是DeepSeek生成的:

[label txt=AI生成]为个人网站上的AI生成内容添加显式标识,既是遵守法规的要求,也是对自己和读者负责任的表现。
先审核你网站上现有的内容,区分出哪些是AI生成的。
根据内容类型(文本、图片、视频等),参考表格中的方法,尽快补充添加显式标识
未来发布新内容时,养成先添加标识再发布的习惯。
考虑采用一些工具或技巧来简化标识流程,并关注官方的后续技术标准和指南
希望这份详细的指南能对你有所帮助!如果你对特定类型内容的标识还有更具体的问题,可以告诉我。[/label]

[label txt=AI生成内容 type=warning]这是一段AI生成的文本,请注意甄别真假。[/label]

[label txt=敏感内容 type=danger]这段内容包含敏感信息。[/label]

[label txt=提示信息 type=info]这是一条重要提示。[/label]

[label txt=普通标识]这是普通的标识文本。[/label]

下面是示范图片,由豆包生成 (提示词:墨水书法,一行字:喜欢捣鼓 不断进步 仅文字):

[label txt=此内容AI生成,注意甄别真假] 墨水书法创作(1).png
[/label]

下载插件

添加插件文件夹:LabelMark

Plugin.php文件:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;

/**
 * 内容标识插件,为指定内容添加标识遮罩或标签
 * 
 * @package LabelMark
 * @author 寒士杰克
 * @version 1.0.1
 * @link http://www.hansjack.com
 */
class LabelMark_Plugin implements Typecho_Plugin_Interface
{
    /**
     * 激活插件方法,如果激活失败,直接抛出异常
     * 
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function activate()
    {
        Typecho_Plugin::factory('Content')->LabelMark = array('LabelMark_Plugin', 'parse');
    }
    
    /**
     * 禁用插件方法,如果禁用失败,直接抛出异常
     * 
     * @access public
     * @return void
     * @throws Typecho_Plugin_Exception
     */
    public static function deactivate(){}
    
    /**
     * 获取插件配置面板
     * 
     * @access public
     * @param Typecho_Widget_Helper_Form $form 配置面板
     * @return void
     */
    public static function config(Typecho_Widget_Helper_Form $form)
    {
        $maskBackground = new Typecho_Widget_Helper_Form_Element_Text(
            'maskBackground', 
            NULL, 
            'rgba(0, 0, 0, 0.8)', 
            _t('遮罩背景色'),
            _t('设置图片和视频遮罩层的背景色,支持rgba格式')
        );
        $form->addInput($maskBackground);
        
        $textLabelColor = new Typecho_Widget_Helper_Form_Element_Text(
            'textLabelColor', 
            NULL, 
            '#ff6b6b', 
            _t('文本标签颜色'),
            _t('设置文本标签的背景颜色')
        );
        $form->addInput($textLabelColor);
        
        $textContentBg = new Typecho_Widget_Helper_Form_Element_Text(
            'textContentBg', 
            NULL, 
            '#fff3cd', 
            _t('文本内容背景色'),
            _t('设置被标识文本内容的背景颜色,建议使用淡色')
        );
        $form->addInput($textContentBg);
        
        $textContentBorder = new Typecho_Widget_Helper_Form_Element_Text(
            'textContentBorder', 
            NULL, 
            '#ffeaa7', 
            _t('文本内容边框色'),
            _t('设置被标识文本内容的边框颜色')
        );
        $form->addInput($textContentBorder);
        
        $labelPosition = new Typecho_Widget_Helper_Form_Element_Radio(
            'labelPosition',
            array(
                'top-right' => _t('右上角'), 
                'top-left' => _t('左上角'),
                'bottom-right' => _t('右下角'),
                'bottom-left' => _t('左下角')
            ),
            'top-right',
            _t('标签位置'),
            _t('设置文本标签显示的位置')
        );
        $form->addInput($labelPosition);
        
        $animation = new Typecho_Widget_Helper_Form_Element_Radio(
            'animation',
            array('enable' => _t('启用'), 'disable' => _t('禁用')),
            'enable',
            _t('动画效果'),
            _t('是否启用悬停动画效果')
        );
        $form->addInput($animation);
    }
    
    /**
     * 个人用户的配置面板
     * 
     * @access public
     * @param Typecho_Widget_Helper_Form $form
     * @return void
     */
    public static function personalConfig(Typecho_Widget_Helper_Form $form){}
    
    /**
     * 插件实现方法
     * 
     * @access public
     * @param string $content 文章内容
     * @return string
     */
    public static function parse($content)
    {
        $options = Helper::options();
        
        // 获取插件配置
        $pluginConfig = $options->plugin('LabelMark');
        $maskBackground = isset($pluginConfig->maskBackground) ? $pluginConfig->maskBackground : 'rgba(0, 0, 0, 0.8)';
        $textLabelColor = isset($pluginConfig->textLabelColor) ? $pluginConfig->textLabelColor : '#ff6b6b';
        $textContentBg = isset($pluginConfig->textContentBg) ? $pluginConfig->textContentBg : '#fff3cd';
        $textContentBorder = isset($pluginConfig->textContentBorder) ? $pluginConfig->textContentBorder : '#ffeaa7';
        $labelPosition = isset($pluginConfig->labelPosition) ? $pluginConfig->labelPosition : 'top-right';
        $animation = isset($pluginConfig->animation) ? $pluginConfig->animation : 'enable';
        
        // 添加CSS样式
        self::addStyles($maskBackground, $textLabelColor, $textContentBg, $textContentBorder, $labelPosition, $animation);
        
        // 解析标签
        $content = self::parseLabels($content);
        
        return $content;
    }
    
    /**
     * 添加样式
     */
    private static function addStyles($maskBackground, $textLabelColor, $textContentBg, $textContentBorder, $labelPosition, $animation)
    {
        static $styleAdded = false;
        if ($styleAdded) return;
        
        $animationClass = $animation == 'enable' ? 'labelmark-animation' : '';
        
        // 根据位置设置标签位置样式
        $labelPositionStyle = self::getLabelPositionStyle($labelPosition);
        
        $css = <<<CSS
<style>
.labelmark-container {
    position: relative;
    display: inline-block;
    vertical-align: top;
}

.labelmark-text-container {
    position: relative;
    display: inline-block;
    background: {$textContentBg};
    border: 2px solid {$textContentBorder};
    border-radius: 6px;
    padding: 8px 12px;
    margin: 4px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    min-height: 20px;
    line-height: 1.5;
}

.labelmark-text-content {
    position: relative;
    z-index: 1;
}

.labelmark-media-container {
    position: relative;
    display: inline-block;
}

.labelmark-mask {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: {$maskBackground};
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    z-index: 10;
    cursor: pointer;
    border-radius: 4px;
    opacity: 1;
    visibility: visible;
}

.labelmark-media-container:hover .labelmark-mask {
    opacity: 0;
    visibility: hidden;
}

.labelmark-text-label {
    position: absolute;
    {$labelPositionStyle}
    background: {$textLabelColor};
    color: white;
    font-size: 11px;
    padding: 3px 8px;
    border-radius: 12px;
    font-weight: bold;
    z-index: 5;
    white-space: nowrap;
    box-shadow: 0 2px 6px rgba(0,0,0,0.3);
    border: 2px solid rgba(255,255,255,0.2);
}

/* 不同位置的标签样式 */
.labelmark-text-label.position-top-right {
    top: -10px;
    right: -10px;
}

.labelmark-text-label.position-top-left {
    top: -10px;
    left: -10px;
}

.labelmark-text-label.position-bottom-right {
    bottom: -10px;
    right: -10px;
}

.labelmark-text-label.position-bottom-left {
    bottom: -10px;
    left: -10px;
}

/* 动画效果 */
.labelmark-animation .labelmark-mask {
    transition: all 0.3s ease;
}

.labelmark-animation .labelmark-text-label {
    transition: all 0.3s ease;
}

.labelmark-animation .labelmark-text-container {
    transition: all 0.3s ease;
}

.labelmark-animation .labelmark-text-container:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

.labelmark-animation .labelmark-text-container:hover .labelmark-text-label {
    transform: scale(1.1);
    box-shadow: 0 3px 8px rgba(0,0,0,0.4);
}

/* 响应式设计 */
@media (max-width: 768px) {
    .labelmark-text-label {
        font-size: 9px;
        padding: 2px 6px;
    }
    
    .labelmark-mask {
        font-size: 14px;
    }
    
    .labelmark-text-container {
        padding: 6px 10px;
        margin: 2px;
    }
}

/* 视频特殊处理 */
.labelmark-video-container {
    position: relative;
    display: block;
    width: 100%;
    border-radius: 8px;
    overflow: hidden;
}

.labelmark-video-container video,
.labelmark-video-container iframe {
    width: 100%;
    height: auto;
    display: block;
}

/* 图片特殊处理 */
.labelmark-image-container {
    border-radius: 8px;
    overflow: hidden;
}

.labelmark-image-container img {
    max-width: 100%;
    height: auto;
    display: block;
}

/* 特殊样式:警告类型 */
.labelmark-warning .labelmark-text-container {
    background: #fff3cd;
    border-color: #ffeaa7;
}

.labelmark-danger .labelmark-text-container {
    background: #f8d7da;
    border-color: #f5c6cb;
}

.labelmark-info .labelmark-text-container {
    background: #d1ecf1;
    border-color: #b8daff;
}

/* 脉冲动画 */
@keyframes labelmark-pulse {
    0% {
        transform: scale(1);
    }
    50% {
        transform: scale(1.05);
    }
    100% {
        transform: scale(1);
    }
}

.labelmark-pulse .labelmark-text-label {
    animation: labelmark-pulse 2s infinite;
}
</style>
CSS;
        
        echo $css;
        $styleAdded = true;
    }
    
    /**
     * 获取标签位置样式
     */
    private static function getLabelPositionStyle($position)
    {
        switch($position) {
            case 'top-left':
                return 'top: -10px; left: -10px;';
            case 'bottom-right':
                return 'bottom: -10px; right: -10px;';
            case 'bottom-left':
                return 'bottom: -10px; left: -10px;';
            case 'top-right':
            default:
                return 'top: -10px; right: -10px;';
        }
    }
    
    /**
     * 解析标签内容
     */
    private static function parseLabels($content)
    {
        // 匹配 [label txt=xxx]content[/label] 格式,支持可选的type参数
        $pattern = '/\[label\s+txt=([^\]]+?)(?:\s+type=([^\]]+?))?\](.*?)\[\/label\]/s';
        
        return preg_replace_callback($pattern, function($matches) {
            $labelText = trim($matches[1], '"\'');
            $type = isset($matches[2]) ? trim($matches[2], '"\'') : 'default';
            $innerContent = $matches[3];
            
            // 检测内容类型
            if (self::containsImage($innerContent)) {
                return self::wrapImageContent($innerContent, $labelText, $type);
            } elseif (self::containsVideo($innerContent)) {
                return self::wrapVideoContent($innerContent, $labelText, $type);
            } else {
                return self::wrapTextContent($innerContent, $labelText, $type);
            }
        }, $content);
    }
    
    /**
     * 检测是否包含图片
     */
    private static function containsImage($content)
    {
        return preg_match('/!\[.*?\]\(.*?\)|<img[^>]*>/i', $content);
    }
    
    /**
     * 检测是否包含视频
     */
    private static function containsVideo($content)
    {
        return preg_match('/<video[^>]*>|<iframe[^>]*youtube|<iframe[^>]*vimeo|<iframe[^>]*bilibili/i', $content);
    }
    
    /**
     * 包装图片内容
     */
    private static function wrapImageContent($content, $labelText, $type)
    {
        // 处理 Markdown 格式的图片
        $content = preg_replace_callback('/!\[(.*?)\]\((.*?)\)/', function($matches) {
            $alt = $matches[1];
            $src = $matches[2];
            return '<img src="' . htmlspecialchars($src) . '" alt="' . htmlspecialchars($alt) . '" />';
        }, $content);
        
        $typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
        
        return '<div class="labelmark-container labelmark-media-container labelmark-image-container labelmark-animation ' . $typeClass . '">' .
               $content .
               '<div class="labelmark-mask">' . htmlspecialchars($labelText) . '</div>' .
               '</div>';
    }
    
    /**
     * 包装视频内容
     */
    private static function wrapVideoContent($content, $labelText, $type)
    {
        $typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
        
        return '<div class="labelmark-container labelmark-media-container labelmark-video-container labelmark-animation ' . $typeClass . '">' .
               $content .
               '<div class="labelmark-mask">' . htmlspecialchars($labelText) . '</div>' .
               '</div>';
    }
    
    /**
     * 包装文本内容
     */
    private static function wrapTextContent($content, $labelText, $type)
    {
        $options = Helper::options();
        $pluginConfig = $options->plugin('LabelMark');
        $labelPosition = isset($pluginConfig->labelPosition) ? $pluginConfig->labelPosition : 'top-right';
        
        $typeClass = $type !== 'default' ? 'labelmark-' . $type : '';
        $pulseClass = in_array($type, ['warning', 'danger']) ? 'labelmark-pulse' : '';
        
        return '<span class="labelmark-container labelmark-text-container labelmark-animation ' . $typeClass . ' ' . $pulseClass . '">' .
               '<span class="labelmark-text-content">' . $content . '</span>' .
               '<span class="labelmark-text-label position-' . $labelPosition . '">' . htmlspecialchars($labelText) . '</span>' .
               '</span>';
    }
}
?>

使用教程

1、添加渲染

在文章内容输出前添加LableMark的渲染:
比如BearSimple主题,原输出 (post.php文件):

<?php echo reEmoPost(ShortCode($this->content,$this,$this->user->hasLogin(),$this->fields->ArticleType)); ?>

将 LabelMark 插件的处理加入到现有的处理链中:

<?php 
// 获取原始内容
$content = $this->content;
// 先应用 LabelMark 插件处理
$content = Typecho_Plugin::factory('Content')->LabelMark($content);
// 然后应用你原有的其他处理函数
echo reEmoPost(ShortCode($content, $this, $this->user->hasLogin(), $this->fields->ArticleType)); 
?>

或者,如果你希望 LabelMark 在其他处理之后执行:

<?php 
// 获取原始内容
$content = $this->content;
// 先应用原有的处理函数
$content = reEmoPost(ShortCode($content, $this, $this->user->hasLogin(), $this->fields->ArticleType));
// 最后应用 LabelMark 插件处理
$content = Typecho_Plugin::factory('Content')->LabelMark($content);
echo $content;
?>

[label txt=AI生成]我推荐使用第一种方案,让 LabelMark 先处理,因为:
LabelMark 处理的是原始 Markdown 格式的 label 标签
ShortCode 可能也会处理一些类似的标签格式
reEmoPost 处理表情符号[/label]

备注内容

如果上述方法仍然有内容渲染问题,也可以考虑修改插件的激活方式,让它自动集成到 Typecho 的内容处理流程中:

修改 Plugin.php 中的 activate 方法:

public static function activate()
{
    // 使用 Typecho 的内容过滤器
    Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('LabelMark_Plugin', 'parse');
    Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('LabelMark_Plugin', 'parse');
}

这样插件会自动在内容输出时处理,你就不需要在模板中手动调用了,直接使用原来的输出

2、添加文章内容

[label txt=标签示例,仅供参考,部分不适用 type=info] image.png[/label]