You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DjangoBlog/plugins/external_links/plugin.py

77 lines
4.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#gq:
# 导入正则表达式模块用于匹配和处理HTML中的<a>标签
import re
# 导入URL解析模块用于解析链接的域名等信息
from urllib.parse import urlparse
# 导入Django博客系统的插件基类当前插件需继承此类实现标准化功能
from djangoblog.plugin_manage.base_plugin import BasePlugin
# 导入插件钩子管理模块,用于注册和触发插件功能
from djangoblog.plugin_manage import hooks
# 导入文章内容钩子常量,指定插件要作用的具体钩子位置
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
# 定义外部链接处理插件类继承自插件基类BasePlugin
class ExternalLinksPlugin(BasePlugin):
# 插件名称,用于在插件管理界面展示
PLUGIN_NAME = '外部链接处理器'
# 插件功能描述,说明插件的核心作用
PLUGIN_DESCRIPTION = '自动为文章中的外部链接添加 target="_blank" 和 rel="noopener noreferrer" 属性。'
# 插件版本号,用于版本管理和更新识别
PLUGIN_VERSION = '0.1.0'
# 插件作者信息
PLUGIN_AUTHOR = 'liangliangyy'
# 注册插件钩子的方法,插件加载时会自动调用
def register_hooks(self):
# 将当前插件的process_external_links方法注册到文章内容处理钩子上
# 意味着文章内容渲染前,会自动执行该方法处理链接
hooks.register(ARTICLE_CONTENT_HOOK_NAME, self.process_external_links)
# 核心方法:处理文章内容中的外部链接
# 参数content为文章原始HTML内容*args和**kwargs用于接收额外参数预留扩展性
def process_external_links(self, content, *args, **kwargs):
# 导入Django工具函数用于获取当前网站的域名如example.com
from djangoblog.utils import get_current_site
# 获取当前网站的域名,用于判断链接是否为"外部链接"
site_domain = get_current_site().domain
# 定义正则表达式用于匹配HTML中的<a>标签
# 匹配规则:<a 开头 + 任意属性(可选) + href=" + 链接地址 + " + 剩余属性及闭合标签
# re.IGNORECASE表示忽略大小写如<A>和<a>都能匹配)
link_pattern = re.compile(r'(<a\s+(?:[^>]*?\s+)?href=")([^"]*)(".*?/a>)', re.IGNORECASE)
# 定义正则替换的回调函数,每匹配到一个<a>标签就会执行一次
def replacer(match):
# 拆分匹配结果为3个分组
# group(1)<a 到 href=" 的部分
# group(2)href属性中的链接地址
# group(3)href=" 之后到 </a> 的部分
href = match.group(2)
# 检查当前<a>标签是否已包含target属性不区分大小写如Target或TARGET
# 若已存在target属性则不做处理直接返回原标签
if 'target=' in match.group(0).lower():
return match.group(0)
# 解析当前链接地址,获取其域名、路径等结构化信息
parsed_url = urlparse(href)
# 判断链接是否为外部链接:
# 1. parsed_url.netloc不为空排除无域名的链接如相对路径 /about
# 2. 链接的域名netloc不等于当前网站域名site_domain
if parsed_url.netloc and parsed_url.netloc != site_domain:
# 若为外部链接在href闭合后添加 target="_blank"(新窗口打开)
# 和 rel="noopener noreferrer"(安全属性,防止窗口劫持)
return f'{match.group(1)}{href}" target="_blank" rel="noopener noreferrer"{match.group(3)}'
# 若为内部链接(或无域名链接),不做修改,直接返回原标签
return match.group(0)
# 使用正则表达式替换文章内容中的所有<a>标签执行replacer回调函数
# 返回处理后的文章内容
return link_pattern.sub(replacer, content)
# 实例化插件类,使插件系统能识别并加载该插件
plugin = ExternalLinksPlugin()