Mô đun:Citron/Spam

local p = {}
local lang = mw.language.getContentLanguage()

-- Initialize

local title = mw.title.new('MediaWiki:BlockedExternalDomains.json')
local content = title:getContent()
local data = mw.text.jsonDecode(content)
local domains = {}
local domainLookup = {}

for _, entry in ipairs(data) do
table.insert(domains, entry.domain)
end

for _, domain in ipairs(domains) do
domainLookup[domain] = true
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(
""
.. revision.user
.. " ("
.. 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("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(
""
.. 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 Tên miền bị chặn. "
.. tostring(
mw.html.create("span")
:addClass("citron-spam-info")
:wikitext("ⓘ")
)
)
)
: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)
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