跳转到内容

模組:沙盒/PexEric/1

维基百科,自由的百科全书

这是本页的一个历史版本,由PexEric留言 | 贡献2025年5月31日 (六) 16:35编辑。这可能和当前版本存在着巨大的差异。

local p = {}

-- 配置各种评选类型的参数
local assessment_configs = {
    DYKC = {
        title = "Wikipedia:新条目推荐/候选",
        pattern = "%{{!}}%s*article%s*=%s*(.-)%s*[%}%{{!}}]|",
        black = nil,
        blackregex = nil
    },
    PR = {
        title = "Wikipedia:同行评审/提案区",
        pattern = "====%s*(.-)%s*===",
        black = nil,
        blackregex = nil
    },
    GAC = {
        title = "Wikipedia:優良條目評選/提名區",
        pattern = "====%s*(.-)%s*===%f[^=]",
        black = nil,
        blackregex = "==%s*(.-)%s*="
    },
    FAC = {
        title = "Wikipedia:典范条目评选/提名区",
        pattern = "====%s*(.-)%s*===%f[^=]",
        black = nil,
        blackregex = "==%s*(.-)%s*="
    },
    FLC = {
        title = "Wikipedia:特色列表评选/提名区",
        pattern = "====%s*(.-)%s*===%f[^=]",
        black = nil,
        blackregex = "==%s*(.-)%s*="
    },
    ITNC = {
        title = "Wikipedia:新闻动态候选",
        pattern = "%{{!}}%s*article%s*=%s*(.-)%s%{{!}}",
        black = nil,
        blackregex = nil
    }
}

-- 辅助函数:获取表格的键名,用于错误提示
local function get_config_keys(tbl)
    local keys = {}
    if type(tbl) ~= "table" then return {} end
    for k, _ in pairs(tbl) do
        table.insert(keys, k)
    end
    table.sort(keys)
    return keys
end

-- 辅助函数:模拟 PatternedCandidateUtils 的核心 getCandidates 逻辑
-- 因为 PatternedCandidateUtils 未导出 getCandidates,我们在此重现其功能
local function get_candidates_for_assessment(assessment_key)
    local config = assessment_configs[assessment_key]
    if not config then return {} end

    local page_content = mw.title.new(config.title):getContent()
    if not page_content then return {} end

    local matches = {}
    local black_map = {}
    if config.black then
        for b_item in mw.text.gsplit(config.black, '|', true) do
            black_map[b_item] = true
        end
    end

    for m in mw.ustring.gmatch(page_content, config.pattern) do
        if not black_map[m] and not (config.blackregex and mw.ustring.match(m, config.blackregex)) then
            -- 候选条目标题中的下划线转换为空格,以获得规范的页面标题
            table.insert(matches, mw.text.trim(m:gsub("_", " ")))
        end
    end
    return matches -- 返回页面标题字符串数组
end

-- 辅助函数:从模板调用字符串中提取模板名称
-- 例如:从 "{{Template:Foo|bar}}" 或 "{{Foo}}" 提取 "Foo"
local function extract_template_name(invocation_str)
    if not invocation_str or type(invocation_str) ~= "string" then return nil end
    -- 匹配 {{ 开头,然后是非 |{}<>[] 的字符作为名称部分
    local name = mw.ustring.match(invocation_str, "^%s*%{%{%s*([^|{}<>%[%]]+)")
    name = name and mw.text.trim(name)
    if name then
        -- 移除 "Template:" 前缀(不区分大小写)
        local t_prefix_lower = "template:"
        local name_lower_part = mw.ustring.lower(mw.ustring.sub(name, 1, #t_prefix_lower))
        if name_lower_part == t_prefix_lower then
            name = mw.text.trim(mw.ustring.sub(name, #t_prefix_lower + 1))
        end
    end
    return name
end

-- WPBS (WikiProject banner shell) 及其重定向的匹配模式
-- (部分借鉴自 Module:PJBSClass/main)
local wpbs_redirect_patterns = {
    "wiki%s*project%s*banner%s*shell", "wpj?%s*banner%s*shell", "wiki%s*project%s*banners",
    "multiple%s*wikiprojects?", "wiki%s*project%s*shell", "pjbs",
    "[維维]基[专專][题題][橫横]幅", "多?[個个]?[維维]?基?[专專][题題][橫横]幅",
    "[維维]基[专專][题題]", "多?[個个]?[維维]?基?[专專][题題]",
    "[专專][题題][橫横]幅", "通用[評评][級级]"
}

-- 检查模板名称是否为 WPBS 或其重定向
local function is_wpbs_template(template_name_input)
    if not template_name_input then return false end
    -- 规范化名称以便比较:小写并替换下划线为空格
    local template_name = mw.ustring.lower(template_name_input:gsub("_", " "))
    for _, pat in ipairs(wpbs_redirect_patterns) do
        if mw.ustring.find(template_name, pat) then
            return true
        end
    end
    return false
end

-- 专题横幅模板名称的常见前缀/模式 (小写, 无空格/下划线)
local project_banner_name_patterns = {
    "wikiproject", "wp", -- 英文
    "維基專題", "維基专题", "专题", "專題" -- 中文
}

-- 检查模板名称是否为一个专题横幅
local function is_project_banner(template_name_input)
    if not template_name_input then return false end
    -- 规范化名称:小写,移除所有空格、下划线、连字符
    local normalized_name = mw.ustring.lower(template_name_input:gsub("[_%s-]", ""))
    for _, pat_prefix in ipairs(project_banner_name_patterns) do
        if mw.ustring.sub(normalized_name, 1, #pat_prefix) == pat_prefix then
            return true
        end
    end
    return false
end

-- 辅助函数:获取指定页面的所有相关专题名称列表
local function get_projects_for_page(page_title_str)
    local projects_set = {} -- 使用集合确保唯一性

    local main_page_title_obj = mw.title.new(page_title_str)
    if not main_page_title_obj then return {} end

    local talk_page_title_obj = main_page_title_obj.talkPageTitle
    if not talk_page_title_obj or not talk_page_title_obj.exists then return {} end

    local talk_content = talk_page_title_obj:getContent()
    if not talk_content or talk_content == "" then return {} end

    local TPV = require("Module:Template parameter value")
    local PJBSClass -- PJBSClass/main 模块

    -- 尝试加载 Module:PJBSClass/main
    local success_pjbs, pjbs_module_or_error = pcall(require, "Module:PJBSClass/main")
    if success_pjbs then
        PJBSClass = pjbs_module_or_error
    else
        -- 如果无法加载 PJBSClass,至少可以继续尝试通用模板匹配
        mw.log("Warning: Module:PJBSClass/main could not be loaded. Falling back to general template scan for projects. Error: " .. tostring(pjbs_module_or_error))
    end

    -- 1. 如果 PJBSClass 加载成功,则优先处理 WikiProject banner shell (WPBS)
    if PJBSClass then
        local wpbs_template_wikitext = PJBSClass.getWPBSTemplateContent(talk_content)
        if wpbs_template_wikitext and mw.text.trim(wpbs_template_wikitext) ~= "" then
            local wpbs_params, wpbs_param_order = TPV.getParameters(wpbs_template_wikitext)
            if wpbs_params and wpbs_param_order then
                for _, key_str in ipairs(wpbs_param_order) do
                    -- WPBS 中的专题横幅通常作为匿名参数(即数字索引)
                    if tonumber(key_str) then
                        local project_invocation = wpbs_params[key_str]
                        local raw_project_name = extract_template_name(project_invocation)
                        if raw_project_name then
                            -- 通过 mw.title.new 获取规范化的模板名 (无 "Template:" 前缀)
                            local project_title_obj = mw.title.new(raw_project_name, "Template")
                            local canonical_project_name = project_title_obj and project_title_obj.text or raw_project_name
                            
                            -- 确认这确实是一个专题横幅
                            if is_project_banner(canonical_project_name) then
                                projects_set[canonical_project_name] = true
                            end
                        end
                    end
                end
            end
        end
    end

    -- 2. 扫描整个讨论页,查找所有独立的专题横幅 (可能不在 WPBS 内,或 WPBS 解析失败时)
    local all_template_invocations = TPV.matchAllTemplates(talk_content)
    for _, invocation_str in ipairs(all_template_invocations) do
        local raw_tpl_name = extract_template_name(invocation_str)
        if raw_tpl_name then
            local tpl_title_obj = mw.title.new(raw_tpl_name, "Template")
            local template_name = tpl_title_obj and tpl_title_obj.text or raw_tpl_name

            -- 添加条件:是专题横幅,且不是 WPBS 本身
            if is_project_banner(template_name) and not is_wpbs_template(template_name) then
                projects_set[template_name] = true
            end
        end
    end
    
    -- 将集合转换为排序列表
    local projects_list = {}
    for proj_name, _ in pairs(projects_set) do
        table.insert(projects_list, proj_name)
    end
    table.sort(projects_list)
    return projects_list
end

--- PUBLIC FUNCTIONS ---

-- 函数1: 列出某个特定内容评选中所有页面对应的专题 (表格形式)
-- 参数: frame.args[1] 或 frame.args.assessment - 评选类型 (DYKC, PR, GAC, FAC, FLC, ITNC)
function p.listProjectsForCandidates(frame)
    local args = frame.args
    local assessment_key_input = args[1] or args.assessment
    
    if not assessment_key_input then
        return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:未提供评选类型。可用类型:' .. table.concat(get_config_keys(assessment_configs), ", "))
            :allDone()
    end
    local assessment_key = mw.ustring.upper(assessment_key_input)

    if not assessment_configs[assessment_key] then
        return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:无效的评选类型 "' ..assessment_key_input .. '"。可用类型:' .. table.concat(get_config_keys(assessment_configs), ", "))
            :allDone()
    end

    local candidates = get_candidates_for_assessment(assessment_key)
    if #candidates == 0 then
        return "该评选类型 (" .. assessment_key .. ") 目前没有候选条目。"
    end

    local root = mw.html.create('table')
    root:addClass('wikitable sortable')
    local header_row = root:tag('tr')
    header_row:tag('th'):css('width', '30%'):wikitext('候选页面')
    header_row:tag('th'):wikitext('所属专题')

    for i, candidate_title_str in ipairs(candidates) do
        local projects = get_projects_for_page(candidate_title_str)
        local row = root:tag('tr')
        row:tag('td'):wikitext('[[' .. candidate_title_str .. ']]')
        
        local projects_display_list = {}
        if #projects > 0 then
            for _, proj_name in ipairs(projects) do
                -- 为专题名称创建链接,通常链接到模板页
                table.insert(projects_display_list, '[[Template:' .. proj_name .. '|' .. proj_name .. ']]')
            end
            row:tag('td'):wikitext(table.concat(projects_display_list, '、'))
        else
            row:tag('td'):css('font-style', 'italic'):wikitext('未找到专题')
        end
    end
    return tostring(root)
end

-- 函数2: 获取指定多个专题对应的特定内容评选项目
-- 参数: frame.args[1] 或 frame.args.assessment - 评选类型
-- 参数: frame.args[2] 或 frame.args.projects   - 逗号分隔的专题名称列表
function p.listCandidatesForProjects(frame)
    local args = frame.args
    local assessment_key_input = args[1] or args.assessment
    local projects_input_str = args[2] or args.projects
    
    if not assessment_key_input then
         return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:未提供评选类型。可用类型:' .. table.concat(get_config_keys(assessment_configs), ", "))
            :allDone()
    end
    local assessment_key = mw.ustring.upper(assessment_key_input)

    if not assessment_configs[assessment_key] then
        return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:无效的评选类型 "' .. assessment_key_input .. '"。可用类型:' .. table.concat(get_config_keys(assessment_configs), ", "))
            :allDone()
    end

    if not projects_input_str or mw.text.trim(projects_input_str) == "" then
        return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:未指定专题列表。请使用逗号分隔专题名称。')
            :allDone()
    end

    local target_projects_set = {}
    local target_projects_display_list = {} -- 用于反馈给用户他们搜索的专题
    for proj_name_raw in mw.text.gsplit(projects_input_str, ',') do
        local proj_name = mw.text.trim(proj_name_raw)
        if proj_name ~= "" then
            -- 规范化用户输入的专题名,以便与提取的专题名比较
            -- 假设 extract_template_name 和 mw.title.new(...).text 返回的是最常见的形式
            -- 用户输入的也应该是这个形式(例如 "WikiProject Medicine", "中国专题")
            target_projects_set[proj_name] = true
            table.insert(target_projects_display_list, proj_name)
        end
    end

    if #target_projects_display_list == 0 then
        return mw.html.create('span')
            :addClass('error')
            :wikitext('错误:指定的专题列表为空或格式不正确。')
            :allDone()
    end

    local candidates = get_candidates_for_assessment(assessment_key)
    if #candidates == 0 then
        return "该评选类型 (" .. assessment_key .. ") 目前没有候选条目。"
    end

    local matching_candidates_titles = {}
    for _, candidate_title_str in ipairs(candidates) do
        local page_projects = get_projects_for_page(candidate_title_str)
        local found_match_for_this_candidate = false
        for _, p_proj_name in ipairs(page_projects) do
            if target_projects_set[p_proj_name] then
                found_match_for_this_candidate = true
                break
            end
        end
        if found_match_for_this_candidate then
            table.insert(matching_candidates_titles, candidate_title_str)
        end
    end

    local feedback_projects_str = table.concat(target_projects_display_list, "、")
    if #matching_candidates_titles == 0 then
        return "在评选类型 " .. assessment_key .. " 中,未找到属于专题“" .. feedback_projects_str .. "”中任何一个的候选条目。"
    else
        local root = mw.html.create()
        root:wikitext("以下是在评选类型 " .. assessment_key .. " 中,属于专题“" .. feedback_projects_str .. "”中任何一个的候选条目:")
        local result_list = root:tag('ul')
        for _, mc_title in ipairs(matching_candidates_titles) do
            result_list:tag('li'):wikitext('[[' .. mc_title .. ']]')
        end
        return tostring(root)
    end
end

return p