local p = {}
local getArgs = require('Module:Arguments').getArgs
-- 1. 修正 conv 函数,处理各种用户相关链接,移除不存在的Special:用户/和IP用户逻辑,增加U:别名
function conv(talk)
talk = talk
:gsub('===(.-)===', '%1') -- 清理标题
-- 处理IP用户贡献页面 (虽然您说没有IP用户,但保留以防万一)
:gsub('Special:用户贡献/(%d+%.%d+%.%d+%.%d+)', 'User:ip:%1')
:gsub('Special:Contributions/(%d+%.%d+%.%d+%.%d+)', 'User:ip:%1')
:gsub('Special:Contribs/(%d+%.%d+%.%d+%.%d+)', 'User:ip:%1')
-- 统一用户相关命名空间为 User:
:gsub('U:', 'User:') -- 添加 U: 别名的支持
:gsub('User_talk:', 'User:') -- 用户讨论页链接
:gsub('用户_talk:', 'User:') -- 中文用户讨论页链接
:gsub('用户:', 'User:') -- 中文用户页链接
:gsub('user:', 'User:') -- 小写英文用户页链接
:gsub('User:~', 'User:~') -- 保留软链接
:gsub('UTC', 'CST') -- 统一时区标记便于匹配
-- 移除阻止解析的标签
:gsub('<pre>.-</pre>', '')
:gsub('<nowiki>.-</nowiki>', '')
return talk
end
function titleStrip(talk)
talk = talk
:gsub('\n===([^\n]-)===', '\n%1\n')
:gsub('<pre>.-</pre>', '')
:gsub('<nowiki>.-</nowiki>', '')
return talk
end
function titleConv(title)
-- 论题转换
title = mw.getCurrentFrame():preprocess(title)
:gsub('<.->', '')
:gsub('%[%[:?(.-)%]%]', function (s) return s:gsub('^.-|', '') end)
:gsub('%[.- (.-)%]', '%1')
:gsub('|', '|')
return title
end
local function unique(t, bArray)
local check = {}
local n = {}
local idx = 1
for k, v in pairs(t) do
if not check[v] then
if bArray then
n[idx] = v
idx = idx + 1
else
n[k] = v
end
check[v] = true
end
end
return n
end
-- 2. 修正 parseTime 函数,适配新的时间格式
local function parseTime(text)
if not text then return nil end
-- 尝试匹配 "YYYY-MM-DD HH:MM" 格式
local _, _, year, month, day, hour, min = text:find '^(%d+)-(%d+)-(%d+) (%d+):(%d+)'
if year then
return os.time {year = tonumber(year), month = tonumber(month), day = tonumber(day), hour = tonumber(hour), min = tonumber(min)}
end
-- 尝试匹配 "YYYY年M月D日 (周) H:M" 格式
local _, _, y, m, d, h, mi = text:find '(%d+)年(%d+)月(%d+)日%s%(.-%)%s*(%d+):(%d+)'
if y then
return os.time {year = tonumber(y), month = tonumber(m), day = tonumber(d), hour = tonumber(h), min = tonumber(mi)}
end
-- 尝试匹配 "H:M, Month D, YYYY (CST)" 格式
local _, _, hour2, min2, day2, month_str, year2 = text:find '(%d+):(%d+),%s*(%w+)%s+(%d+),%s+(%d+)%s*%(CST%)'
if year2 then
local months = {Jan=1,Feb=2,Mar=3,Apr=4,May=5,Jun=6,Jul=7,Aug=8,Sep=9,Oct=10,Nov=11,Dec=12,["一"]=1,["二"]=2,["三"]=3,["四"]=4,["五"]=5,["六"]=6,["七"]=7,["八"]=8,["九"]=9,["十"]=10,["十一"]=11,["十二"]=12}
local month_num = months[month_str:sub(1,3)] or months[month_str]
if month_num then
return os.time {year = tonumber(year2), month = month_num, day = tonumber(day2), hour = tonumber(hour2), min = tonumber(min2)}
end
end
-- 存档标记
if text == '该讨论主题已被存档' then
return os.time {year = 1970, month = 1, day = 1}
end
return nil
end
local function getTimeStyle(time)
if time == '?' or time == '-' or time == '该讨论主题已被存档' then
return 'background-color: var(--custom-closed-topic-no);'
end
local parsed_time = parseTime(time)
if not parsed_time then
return 'background-color: var(--custom-closed-topic-no);'
end
local diff = os.difftime(os.time(), parsed_time) / 86400
if diff >= 30 then
return 'background-color: var(--custom-topic-30-days);'
end
if diff >= 7 then
return 'background-color: var(--custom-topic-7-days);'
end
if diff >= 1 then
return ''
end
return 'background-color: var(--custom-closed-topic-yes);'
end
local function close(text)
if text:match 'closed%-topic%-yes' then
return 'background-color: var(--custom-closed-topic-yes);'
end
if text:match 'closed%-topic%-no' then
return 'background-color: var(--custom-closed-topic-no);'
end
if text:match '已存檔' or text:match '已移動' then
return 'background-color: #ffd;' -- 直接设置颜色
end
return ""
end
local function trim(text)
text = mw.text.killMarkers(text):gsub('^[%s\t\r\n\f]*(.-)[%s\t\r\n\f]*$', '%1')
return text
end
local function makeUserLink(text)
if text == '?' or text == '-' or text == '' then
return text
end
local ipUser = text:match 'ip:(.*)'
if ipUser then
return '[[Special:用户贡献/' .. ipUser .. '|' .. ipUser .. ']]'
else
return '[[User:' .. text .. '|' .. text .. ']]'
end
end
local function getTalkList(pageName)
-- 使用 expandTemplate 获取页面内容,这通常用于跨命名空间或带参数的页面
local talk_content = mw.getCurrentFrame():expandTemplate {title = pageName}
if not talk_content or talk_content == '' then
-- 如果 expandTemplate 失败,尝试直接获取内容
talk_content = mw.title.new( pageName ):getContent()
if not talk_content then
talk_content = '' -- 如果还是失败,给个空字符串
end
end
local talk = conv(talk_content) .. '=='
local talkList = {}
for topic in talk:gmatch '==\n(.-)==' do
talkList[#talkList + 1] = trim(topic)
end
return talkList
end
local function getTitleList(pageName)
local talk_content = mw.getCurrentFrame():expandTemplate {title = pageName}
if not talk_content or talk_content == '' then
talk_content = mw.title.new( pageName ):getContent()
if not talk_content then
talk_content = ''
end
end
local talk = titleStrip(talk_content)
local titleList = {}
for title in talk:gmatch '\n==([^\n]-)==\n' do
titleList[#titleList + 1] = titleConv(trim(title))
end
return titleList
end
-- 3. 修正 getUserInfo 函数,全面匹配用户活动,移除Special:用户/,加入U:
local function getUserInfo(text)
-- 为了排序,我们需要最晚的时间戳
local latestTime = 0
local latestUser = '-'
local userList = {} -- 用于统计总数和去重
-- 匹配所有可能的用户标识:User:, U:, User_talk:, Special:用户贡献/
-- 模式: [User:Name|Display]] ... Time (CST)
for line in text:gmatch("[^\r\n]+") do
local user, time_part = line:match('%[%[User:([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = user
end
userList[#userList + 1] = user
end
-- 匹配 U: 别名
if not user then
user, time_part = line:match('%[%[U:([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = user
end
userList[#userList + 1] = user
end
end
-- 匹配用户讨论页: [[User_talk:Name|Display]] ...
if not user then
user, time_part = line:match('%[%[User_talk:([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = user
end
userList[#userList + 1] = user
end
end
-- 匹配中文用户讨论页
if not user then
user, time_part = line:match('%[%[用户_talk:([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = user
end
userList[#userList + 1] = user
end
end
-- 匹配用户贡献页: [[Special:用户贡献/Name|Name]] ...
if not user then
user, time_part = line:match('%[%[Special:用户贡献/([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = 'ip:' .. user -- 标记为IP用户
end
userList[#userList + 1] = 'ip:' .. user
end
end
-- 匹配英文贡献页
if not user then
user, time_part = line:match('%[%[Special:Contributions/([^|%[\]]-)[^%]]-%]%].-([%d%p%s%w:,,()%(%)%s]+%(CST%))')
if user and time_part then
local parsed_time = parseTime(time_part)
if parsed_time and parsed_time > latestTime then
latestTime = parsed_time
latestUser = 'ip:' .. user
end
userList[#userList + 1] = 'ip:' .. user
end
end
end
-- 如果没有找到任何有效时间,则认为该话题无活跃参与者
if latestTime == 0 then
latestUser = '-'
latestTime = nil
end
local userNum = #userList
local uniqueUserNum = #(unique(userList, true))
local lastUser = latestUser
local lastTime = latestTime and time_part or '?' -- 这里lastTime暂时存储原始时间字符串,用于样式判断
-- 为了向下兼容,我们需要返回时间字符串而不是时间戳
local finalLastTime = '?'
if latestTime and time_part then
finalLastTime = time_part
elseif latestUser == '-' then
finalLastTime = '-'
end
return {userNum = userNum, uniqueUserNum = uniqueUserNum, lastUser = lastUser, lastTime = finalLastTime}
end
-- 4. 修正 generateTable 函数,以符合您提供的格式
local function generateTable(pageName, talkTitle, talkText)
-- 使用新的表格头部
local body = {
'{| class="wikitable sortable mw-collapsible" style="float:left;height:1px;"',
'|-',
'! data-sort-type="number" style="font-weight: normal;" | <small>#</small> !! 💭 話題 !! 進度 !! <span title="發言數/發言人次 (實際上為計算簽名數)">💬</span> !! <span title="參與討論人數/發言人數">👥</span> !! 🙋 最新發言 !! data-sort-type="isoDate" | <span title="最後更新">🕒 <small>(UTC+8)</small></span>'
}
local userInfo, rawTimeStr
local userStyle, uniqueUserStyle, timeStyle, serialStyle
local title, timeFormat, sortValue
for i = 1, #talkText do
userInfo = getUserInfo(talkText[i])
-- 从getUserInfo获取原始时间字符串用于解析和格式化
rawTimeStr = userInfo.lastTime
-- --- 重新计算样式和格式 ---
-- 对表格添加样式
userStyle = (userInfo.userNum == 0 or userInfo.userNum == 1) and 'background-color: var(--custom-closed-topic-no);' or ''
uniqueUserStyle = (userInfo.uniqueUserNum == 0 or userInfo.uniqueUserNum == 1) and 'background-color: var(--custom-closed-topic-no);' or ''
timeStyle = getTimeStyle(rawTimeStr) -- 使用原始时间字符串判断样式
serialStyle = close(talkText[i])
-- --- 格式化时间 ---
timeFormat = ''
sortValue = ''
if rawTimeStr ~= '?' and rawTimeStr ~= '-' and rawTimeStr ~= '该讨论主题已被存档' then
local parsed_timestamp = parseTime(rawTimeStr)
if parsed_timestamp then
-- 生成用于排序的ISO格式
sortValue = 'data-sort-type="isoDate" data-sort-value="' .. os.date('!%Y-%m-%dT%H:%M:%S.000Z', parsed_timestamp) .. '" '
-- 生成用于显示的本地时间 (UTC+8)
local utc8_date = os.date('%Y-%m-%d %H:%M', parsed_timestamp + 8*3600)
local date_part, time_part = utc8_date:match('(.-) (.-)$')
timeFormat = date_part .. ' <span style="color: blue;">' .. time_part .. '</span>'
else
-- 如果解析失败,原样显示
timeFormat = rawTimeStr
end
else
-- 对于问号或存档,不设置排序值,直接显示
if rawTimeStr == '该讨论主题已被存档' then
timeFormat = '已存檔'
else
timeFormat = rawTimeStr
end
end
-- 处理标题过长的问题 (保留原逻辑)
title = talkTitle[i]
if mw.ustring.len(title:gsub('&[a-z0-9]+;', 'x')) - #title:gsub('&[a-z0-9]+;', 'x'):gsub('[^ -~]', '') * 0.5 > 25 then
title = '<span style="color: #111">' .. title .. '</span>' -- 使用您例子中的样式
end
-- --- 组装表格一个话题的部分 ---
table.insert(
body,
table.concat(
{
'|-',
serialStyle ~= '' and ('|-style="background-color: ' .. serialStyle:match('var%([^)]*%)') or serialStyle:match('#%x+') .. '"') or '|-', -- 如果有关闭样式,应用到整行
'| style="text-align: right;" | ' .. i,
'| [[' .. pageName .. '#' .. talkTitle[i]:gsub('{', '{'):gsub('}', '}') .. '|' .. title.. ']]',
'| style="padding: 0;" | ' .. (serialStyle:match('已存檔') and '已存檔' or serialStyle:match('已移動') and '已移動' or '{{MarkAsResolved|status=o}}'), -- 进度栏,这里可以根据talkText[i]内容动态生成,或设为默认
'| style="text-align: right;' .. userStyle .. '" | ' .. (userInfo.userNum == 0 and '0' or userInfo.userNum),
'| style="text-align: right;' .. uniqueUserStyle .. '" | ' .. (userInfo.uniqueUserNum == 0 and '0' or userInfo.uniqueUserNum),
'| ' .. makeUserLink(userInfo.lastUser),
'| ' .. sortValue .. timeFormat -- 插入排序属性和格式化时间
},
'\n'
)
)
end
table.insert(body, '|}')
return table.concat(body, '\n')
end
function p.main(frame)
local args = getArgs(frame)
local f = mw.getCurrentFrame()
local talkTitle, talkText = getTitleList(args[1]), getTalkList(args[1])
if #talkTitle ~= #talkText then
return error 'Topic list获取错误。'
end
-- 对标题进行预处理
for i = 1, #talkTitle do
talkTitle[i] = f:preprocess(talkTitle[i])
end
return generateTable(args[1], talkTitle, talkText)
end
return p