local p = {}
local lang = mw.language.getContentLanguage()
local currentTitle = mw.title.getCurrentTitle()
local currentFullText = currentTitle.fullText
local isANPage = mw.ustring.match(
currentFullText,
"^Wikipedia:Tin nhắn cho bảo quản viên/"
) ~= nil
-- Initialize
local domains = {}
local domainLookup = {}
if not isANPage then
local title = mw.title.new('MediaWiki:BlockedExternalDomains.json')
local content = title:getContent() -- expensive call
local data = mw.text.jsonDecode(content)
for _, entry in ipairs(data) do
table.insert(domains, entry.domain)
end
for _, domain in ipairs(domains) do
domainLookup[domain] = true
end
end
-- Main
local function isAdded(hostname)
return domainLookup[hostname] == true
end
local function statsText(hostnames, feedbacks)
local stats = { good = 0, bad = 0, added = 0 }
for _, hostname in ipairs(hostnames) do
for _, feedback in ipairs(feedbacks) do
if feedback.hostname == hostname.hostname then
if feedback.status == 0 then
stats.good = stats.good + 1
else
stats.bad = stats.bad + 1
end
end
end
if isAdded(hostname.hostname) then
stats.added = stats.added + 1
end
end
local texts = {}
for key, value in pairs(stats) do
if value ~= 0 then
table.insert(texts, tostring(
mw.html.create("span")
:addClass("citron-spam-" .. key .. "-count")
:wikitext(value)
))
end
end
return table.concat(texts, " · ")
end
local function createHostnameListNode(title, node, hostnames, revisions, reportDate, id, feedbacks)
if #hostnames == 0 then
return nil
end
node
:node(
mw.html.create("div")
:addClass("citron-spam-content-title-placeholder mw-collapsible"
.. (id:find("uncertain") and " mw-collapsed" or ""))
:css("position", "absolute")
:attr("id", "mw-customcollapsible-" .. id)
)
:node(
mw.html.create("div")
:addClass("citron-spam-content-title-wrapper mw-customtoggle-" .. id)
:node(
mw.html.create("div")
:addClass("citron-spam-content-title")
:wikitext(title .. " (" .. #hostnames .. ")")
)
:node(
mw.html.create("div")
:addClass("citron-spam-content-feedback-stats")
:wikitext(statsText(hostnames, feedbacks))
)
)
for _, hostname in ipairs(hostnames) do
-- .citron-spam-hostname-inner-bottom
local hostnameInnerBottomNode = mw.html.create("div")
:addClass("citron-spam-hostname-inner-bottom mw-collapsible mw-collapsed")
:attr("id", "mw-customcollapsible-citron-" .. reportDate)
for _, revisionId in ipairs(hostname.revisionIds) do
local revision = revisions[revisionId]
hostnameInnerBottomNode
:node(
mw.html.create("div")
:addClass("citron-spam-revision")
:wikitext(
"[[Special:Contributions/"
.. revision.user
.. "|"
.. revision.user
.. "]] ([[Special:Diff/"
.. revisionId
.. "|"
.. revision.page
.. "]])"
)
)
end
-- .citron-spam-feedbacks
local feedbacksNode = mw.html.create("div")
:addClass("citron-spam-feedbacks")
local feedbacksCount = 0
for _, feedback in ipairs(feedbacks) do
if feedback.hostname == hostname.hostname then
feedbacksCount = feedbacksCount + 1
feedbacksNode:node(
mw.html.create("div")
:addClass("citron-spam-feedback "
.. (feedback.status == 0
and "citron-spam-feedback-good"
or "citron-spam-feedback-bad"))
:attr("title", feedback.createdAt)
:node(mw.html.create("span")
:addClass("citron-spam-feedback-icon"))
:node(mw.html.create("span")
:addClass("citron-spam-feedback-user")
:wikitext(feedback.user))
)
end
end
local statusClass, bgHostnameInnerTopLeftStart, bgHostnameInnerTopLeftEnd
if hostname.score >= 0.8 then
statusClass = "citron-spam-very-bad"
bgHostnameInnerTopLeftStart = "var(--background-color-destructive-subtle--hover, #ffdad3)"
bgHostnameInnerTopLeftEnd = "var(--background-color-destructive-subtle, #ffe9e5)"
elseif hostname.score >= 0.5 then
statusClass = "citron-spam-bad"
bgHostnameInnerTopLeftStart = "light-dark(#ffdcb8, #572c19)"
bgHostnameInnerTopLeftEnd = "light-dark(#ffead4, #361d13)"
else
statusClass = "citron-spam-uncertain"
bgHostnameInnerTopLeftStart = "var(--background-color-progressive-subtle--hover, #dce3f9)"
bgHostnameInnerTopLeftEnd = "var(--background-color-progressive-subtle, #f1f4fd)"
end
local added = isAdded(hostname.hostname)
local addedClass = added and "citron-spam-added" or "citron-spam-not-added"
local addedText = added and "Đã thêm" or "Chưa thêm"
local score = string.format("%.0f%%", hostname.score * 100)
node
:node(
mw.html.create("div")
:addClass("citron-spam-hostname mw-collapsible"
.. (id:find("uncertain") and " mw-collapsed" or ""))
:attr("id", "mw-customcollapsible-" .. id)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-time")
:wikitext(hostname.time)
)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-inner")
:attr("data-added", tostring(added))
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-inner-top")
:css("--percentage", score)
:css("--background-hostname-inner-top-left-start", bgHostnameInnerTopLeftStart)
:css("--background-hostname-inner-top-left-end", bgHostnameInnerTopLeftEnd)
:css("--background-hostname-inner-top-left",
"linear-gradient(to right, var(--background-hostname-inner-top-left-start) var(--percentage), var(--background-hostname-inner-top-left-end) var(--percentage))")
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-inner-top-left "
.. statusClass)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-hostname")
:wikitext(hostname.hostname)
)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-score")
:wikitext(score)
)
)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-inner-top-right")
:node(feedbacksCount ~= 0
and feedbacksNode
or nil)
:node(
mw.html.create("div")
:addClass("citron-spam-hostname-status "
.. addedClass)
:wikitext(addedText)
)
)
)
:node(hostnameInnerBottomNode)
)
)
end
return node
end
local function createReport(date)
local today = os.date("%Y-%m-%d") == date;
local title = mw.title.new("Project:Citron/Spam/" .. date .. ".json")
local content = title:getContent()
local wrapperNode = mw.html.create("div")
:addClass("citron-spam-wrapper")
:node(
mw.html.create("div")
:addClass("citron-spam-header")
:node(
mw.html.create("div")
:addClass("citron-spam-title")
:wikitext("[[Project:Citron/Spam|Citron · Spam]]")
)
:node(
mw.html.create("div")
:addClass("citron-spam-actions")
:node(
mw.html.create("span")
:addClass("mw-customtoggle-citron-" .. date)
:wikitext("Xem phiên bản")
)
:node(
today
and mw.html.create("span")
:addClass("citron-spam-support")
:attr("data-date", date)
:wikitext("Hỗ trợ đánh giá")
or nil
)
:node(
mw.html.create("span")
:addClass("citron-spam-date")
:wikitext(
"[[Project:Citron/Spam/"
.. date
.. ".json|"
.. date
.. "]]"
)
)
)
)
if content == nil then
wrapperNode:node(
mw.html.create("div")
:addClass("citron-spam-empty")
:wikitext("Không có báo cáo tại thời điểm được cung cấp")
)
return tostring(wrapperNode)
end
local data = mw.text.jsonDecode(content)
local very_bad_hostnames = {}
local bad_hostnames = {}
local uncertain_hostnames = {}
for _, hostname in ipairs(data.hostnames) do
if hostname.score >= 0.8 then
table.insert(very_bad_hostnames, hostname)
elseif hostname.score >= 0.5 then
table.insert(bad_hostnames, hostname)
else
table.insert(uncertain_hostnames, hostname)
end
end
local contentVeryBadNode = createHostnameListNode(
"Có khả năng rất cao là xấu",
mw.html.create("div"):addClass("citron-spam-content"),
very_bad_hostnames,
data.revisions,
date,
"citron-" .. date .. "-very-bad",
data.feedbacks
)
local contentBadNode = createHostnameListNode(
"Có khả năng cao là xấu",
mw.html.create("div"):addClass("citron-spam-content"),
bad_hostnames,
data.revisions,
date,
"citron-" .. date .. "-bad",
data.feedbacks
)
local contentUncertainNode = createHostnameListNode(
"Chưa chắc chắn",
mw.html.create("div"):addClass("citron-spam-content"),
uncertain_hostnames,
data.revisions,
date,
"citron-" .. date .. "-uncertain",
data.feedbacks
)
if #data.hostnames > 0 then
wrapperNode:node(contentVeryBadNode)
wrapperNode:node(contentBadNode)
wrapperNode:node(contentUncertainNode)
else
wrapperNode:node(
mw.html.create("div")
:addClass("citron-spam-empty")
:wikitext("Chưa có dữ liệu")
)
end
wrapperNode
:node(
mw.html.create("div")
:addClass("citron-spam-footer")
:node(
mw.html.create("div")
:addClass("citron-spam-last-update")
:wikitext(
"Cập nhật lúc "
.. lang:formatDate("H:i, j F, Y", data.updatedAt)
.. " (UTC). Truy cập [[Special:BlockedExternalDomains|Tên miền bị chặn]]. "
.. tostring(
mw.html.create("span")
:addClass("citron-spam-info")
:wikitext("[[:vi:User:Plantaest/Citron|ⓘ]]")
)
)
)
:node(
mw.html.create("div")
:addClass("citron-spam-status")
:attr("data-today", tostring(today))
:wikitext(today and "Đang cập nhật" or "Kết thúc cập nhật")
)
)
return tostring(wrapperNode)
end
local function createTemplatestylesNode()
return mw.getCurrentFrame():extensionTag{
name = "templatestyles", args = { src = "Module:Citron/Spam/styles.css" }
}
end
function p.report(frame)
if isANPage then
local date = frame.args["date"]
or lang:formatDate("Y-m-d")
return string.format(
"Xem báo cáo tại: [[Wikipedia:Citron/Spam/%s.json]].",
date
)
end
local date = frame.args["date"]
return createTemplatestylesNode() .. createReport(date)
end
function p.main(frame)
local dates = {}
local reports = {}
for i = 0, 6 do
table.insert(
dates,
lang:formatDate("Y-m-d", "-" .. i .. " days")
)
end
for _, date in ipairs(dates) do
table.insert(reports, createReport(date))
end
return createTemplatestylesNode() .. table.concat(reports, "\n")
end
return p