Module:Sandbox/Aidan9382/DiscussionOverview
< Module:Sandbox | Aidan9382
--[=[
A module designed to provide an overall summary and some statistics on a discussion board
Inspired by, and partially borrowed from, [[Module:Sandbox/Smalljim/DiscussionIndexTest]]
--]=]
local Transcluder = require("Module:Transcluder")
local p = {}
table.find = function(t,o) --Used to luau, so assumed this existed. Heres a quick version
for a,b in next,t do
if b == o then
return true
end
end
return false
end
local lang = mw.getContentLanguage()
local function getTime(timestamp)
return tonumber(lang:formatDate("U",timestamp))
end
local function concatUsers(users)
local final = ""
for _,user in next,users do
if string.match(user,"^%d+%.%d+%.%d+%.%d+$") or string.match(user,"^%x+:[%x:]+$") or string.match(user, "^~%d%d%d%d%-%d%d%d%d%d%-%d%d%d$") then --Lazy but mostly working basic IPv6 regex
final = final .. "[[Special:Contributions/"..user.."|"..user.."]], "
else
final = final .. "[[:User:"..user.."|"..user.."]], "
end
end
return string.sub(final,1,-3)
end
--Specialised version of Sandbox/Smalljim/ParsePageTest.formatDateDiff that just does hours (better for sorting cause im not learning wikitables)
local function formatDateDiff( date_diff )
return tonumber( math.floor(date_diff/(6*60))/10 ) .. ' hours';
end
--Specialised version of Transcluder.getSections, using a similar design
local function getSectionData(text)
local sections = {}
text = "\n"..text.."\n== "
while true do
local section,content = string.match(text,"\n==%s*([^=]-)%s*==\n(.-)\n==[^=]")
if not section then
break
end
text = string.sub(text,string.find(text,content,1,true)+#content,-1)
sections[#sections+1] = {name=section,content=content}
end
return sections
end
--This is a bloody mess of a mix of ideas, but it mostly works, so good enough
local function getUserMentions(text)
--Returns a list of users, and if they were considered a "participant" or someone who was just mentioned
local mentions = {}
text = mw.text.decode(text,true)
--Timestamp is %d%d:%d%d, %d%d? %w+ %d%d%d%d %(UTC%)) but we allow some (minor) leniancy for those who just slightly edit their dates (why?) so that it still picks up
local timestampRegex = "((%d%d:%d%d, %d%d? %w+,? %d%d%d%d) %(UTC%))"
local userRegex = "(%[%[:?[Uu]ser:([^#|%]]+))"
local userTalkRegex = "(%[%[:?[Uu]ser[ _][Tt]alk:([^#|%]]+))"
local userContribRegex = "(%[%[:?[Ss]pecial:[Cc]ontributions/([^#|%]]+))"
for line in string.gmatch(text,"[^\n]+") do
--Split by line and check all content on said line. This assumes all signatures never use newlines, which they should not be doing anyways.
--Bar of entry for being labelled a "participant" is a valid timestamp along with their user/usertalk/contribs
--Users can be noted as being both a participant and a mention during the data, so be smart in using this data
local usersOnThisLine = {}
for _,reg in next,{userRegex,userTalkRegex,userContribRegex} do
local index = 1
while true do
local targetText = string.sub(line,index,-1)
local wholeText,identifier = string.match(targetText,reg)
if not wholeText then
break
end
identifier = string.gsub(identifier,"_"," ")
if not string.find(identifier,"/") then --Avoid subpage nonsense
usersOnThisLine[string.find(targetText,reg)] = identifier
end
index = index + string.find(targetText,reg) + #wholeText
end
end
--Start associating timestamps with users
local index = 1
local pindex = {} --Lazy coding
local participants = {}
while true do
local targetText = string.sub(line,index,-1)
local wholeText,identifier = string.match(targetText,timestampRegex)
if not wholeText then
break
end
--Backtrack through the text for a mention
local timestampLocation = string.find(targetText,identifier)
local user,where
for i = timestampLocation,1,-1 do
user,where = usersOnThisLine[i],i
if user then
break
end
end
if user then
participants[#participants+1] = {user=user,when=identifier,participated=true}
pindex[user] = true
else --else: be confused as hell
mw.log("Timestamp had no associated users on its line (TS "..wholeText..")")
end
index = index + timestampLocation + #wholeText
end
local pings = {}
for _,user in next,usersOnThisLine do
if not pings[user] and not pindex[user] then --If they participated on a line, just ignore all pings
pings[user] = true
end
end
--Integrate the new data
for user,_ in next,pings do
mentions[#mentions+1] = {user=user,participated=false}
end
for _,userData in next,participants do
mentions[#mentions+1] = userData
end
end
return mentions
end
function p.main(frame)
local page = frame.args[1] or frame.args.page
assert(type(page)=="string","Invalid or no page provided")
local _, text = xpcall(function()
return Transcluder.get(page)
end, function(err)
error(debug.traceback())
end)
local sections = getSectionData(text)
local tableContent = '{| class="wikitable sortable"\n! style="text-align:center" colspan=6 | Conversation summary for [[' .. page .. ']]' ..
'<span style="position:absolute;right:2em;font-size:90%">[[Module:Sandbox/Aidan9382/DiscussionOverview|v]]</span>' ..
'\n|-\n! Section !! Initiator !! Last Comment !! Size !! Participants !! Mentions'
for _,section in next,sections do
local sanitisedName = string.gsub(string.gsub(mw.uri.anchorEncode(frame:preprocess(section.name)),"%[%[:?[^|]-|([^%]]-)]]","%1"),"%[%[:?([^%]]-)]]","%1")
local displayName = string.gsub(string.gsub(frame:preprocess(section.name),"%[%[:?[^|]-|([^%]]-)]]","%1"),"%[%[:?([^%]]-)]]","%1")
local wikilinkAnchor = "[[:"..page.."#"..sanitisedName.."|"..displayName.."]]"
local membersInText = getUserMentions(section.content)
local uniqueParticipants = {}
for _,userData in next,membersInText do
if userData.participated and not table.find(uniqueParticipants,userData.user) then
uniqueParticipants[#uniqueParticipants+1] = userData.user
end
end
local mentionedUsers = {}
for _,userData in next,membersInText do
if not userData.participated and not table.find(uniqueParticipants,userData.user) and not table.find(mentionedUsers,userData.user) then
mentionedUsers[#mentionedUsers+1] = userData.user
end
end
local now = getTime()
local orderedComments = {}
for _,userData in next,membersInText do
if userData.participated then
local when = getTime(userData.when)
if now > when then --Ensure comment is from the past
if #orderedComments == 0 then
orderedComments[#orderedComments+1] = {user=userData.user,when=when}
else
for i = 1,#orderedComments do
local comment = orderedComments[i]
if when < comment.when then
for i2 = #orderedComments+1,i,-1 do
orderedComments[i2] = orderedComments[i2-1]
end
orderedComments[i] = {user=userData.user,when=when}
break
end
if i == #orderedComments then
orderedComments[#orderedComments+1] = {user=userData.user,when=when} --Reached the end, latest comment
break
end
end
end
end
end
end
local firstComment,lastComment
if #orderedComments == 0 then
firstComment = "N/A"
lastComment = "N/A"
elseif #orderedComments == 1 then
firstComment = formatDateDiff(now-orderedComments[1].when) .. " ago" .. "<br>" .. concatUsers({orderedComments[1].user})
lastComment = "N/A"
else
firstComment = formatDateDiff(now-orderedComments[1].when) .. " ago" .. "<br>" .. concatUsers({orderedComments[1].user})
lastComment = formatDateDiff(now-orderedComments[#orderedComments].when) .. " ago" .. "<br>" .. concatUsers({orderedComments[#orderedComments].user})
end
local participants = #uniqueParticipants .. ": " .. concatUsers(uniqueParticipants)
local mentions = #mentionedUsers .. ": " .. concatUsers(mentionedUsers)
local sectionContent = "\n|-\n| "..wikilinkAnchor.." || "..firstComment.." || "..lastComment.." || "..#section.content.." || "..participants.." || "..mentions
tableContent = tableContent .. sectionContent
end
return tableContent .. "\n|}"
end
function p.maindev(frame)
local content = p.main(frame)
return content .. "\n\n" .. frame:extensionTag("syntaxhighlight",content,{lang="html5"})
end
p.getSectionData = getSectionData
p.getUserMentions = getUserMentions
return p