Немає опису редагування
Мітка: Скасовано
Немає опису редагування
 
(Не показано 13 проміжних версій цього користувача)
Рядок 1: Рядок 1:
local p = {}
local p = {}


--- Функція для виведення помилки на сторінку
-- ================================================
local function error_output(context, error_details)
-- КЕШУВАННЯ (найважливіша оптимізація)
    local safe_details = error_details or "N/A"
-- ================================================
    return string.format("<span style='color:red; font-weight:bold;'>[FD2 Error: %s. Details: %s]</span>",
        context or "Unknown Context", safe_details)
end


--- Видаляє вікі-посилання [[link|text]] і [[link]]
local page_cache = {}
local function clean_wikilinks(text)
local table_cache = {}
    if not text then return nil end
    -- [[link|text]] -> text
    text = mw.ustring.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    -- [[link]] -> link
    text = mw.ustring.gsub(text, "%[%[([^%]]+)%]%]", "%1")
    return mw.text.trim(text)
end


----------------------------------------------------------------------
local function get_page_content(page_name)
-- ГОЛОВНИЙ ПАРСЕР ДЛЯ ГОРИЗОНТАЛЬНИХ ТАБЛИЦЬ
    if page_cache[page_name] then
----------------------------------------------------------------------
        return page_cache[page_name]
local function fetch_from_table(page_title, player_name, column_index)
    end
      
      
     if not player_name or player_name == "" then
    local title = mw.title.new(page_name)
         return error_output("No Player Name", page_title)
     if not title or not title.exists then
        page_cache[page_name] = nil
         return nil
     end
     end
      
      
     local title = mw.title.new(page_title)
     local content = title:getContent()
     if not title or not title.exists then  
    page_cache[page_name] = content
         return error_output("Page Missing", page_title)
    return content
end
 
local function get_table_from_page(page_name)
     if table_cache[page_name] then
         return table_cache[page_name]
     end
     end
      
      
     local content = title:getContent()
     local content = get_page_content(page_name)
     if not content then  
     if not content then  
         return error_output("Content Read Fail", page_title)
        table_cache[page_name] = nil
         return nil
     end
     end
      
      
    -- Знаходимо таблицю
     local table_start = mw.ustring.find(content, "{|")
     local table_start = mw.ustring.find(content, "{|")
     local table_end = mw.ustring.find(content, "|}", table_start)
     local table_end = mw.ustring.find(content, "|}", table_start)
      
      
     if not table_start or not table_end then
     if not table_start or not table_end then
         return error_output("Table Missing", page_title)
        table_cache[page_name] = nil
         return nil
     end
     end
      
      
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
      
     table_cache[page_name] = table_content
     -- Екрануємо спеціальні символи в імені гравця
     return table_content
     local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
end
     local player_pattern = "%[%[" .. escaped .. "%]%]"
 
      
-- ================================================
    -- Шукаємо рядки, які починаються з |-
-- УТИЛІТИ (спільні для всіх функцій)
-- ================================================
 
local function error_output(context, error_details)
    local safe_details = error_details or "N/A"
     return string.format("<span style='color:red; font-weight:bold;'>[Error: %s. Details: %s]</span>",
        context or "Unknown", safe_details)
end
 
local function clean_wikilinks(text)
    if not text then return nil end
    text = mw.ustring.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    text = mw.ustring.gsub(text, "%[%[([^%]]+)%]%]", "%1")
     return mw.text.trim(text)
end
 
local function strip_html_tags(text)
    if not text then return text end
    text = mw.ustring.gsub(text, "<span[^>]*>", "")
    text = mw.ustring.gsub(text, "</span>", "")
     return mw.text.trim(text)
end
 
local function parse_table_rows(table_content)
     local rows = {}
     local rows = {}
     for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
     for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
         table.insert(rows, row)
         table.insert(rows, row)
     end
     end
   
    -- Останній рядок (до |})
     local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
     local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
     if last_row then
     if last_row then
         table.insert(rows, last_row)
         table.insert(rows, last_row)
     end
     end
    return rows
end
local function parse_row_cells(row)
    local cells = {}
   
    for line in mw.ustring.gmatch(row, "[^\n]+") do
        line = mw.text.trim(line)
       
        if line ~= "|-" and line ~= "" then
            line = mw.ustring.gsub(line, "^|%s*", "")
           
            if line ~= "" then
                table.insert(cells, line)
            end
        end
    end
   
    return cells
end
local function find_player_in_table(table_content, player_name, column_index)
    if not table_content then return nil end
   
    local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local player_pattern = "%[%[" .. escaped .. "%]%]"
   
    local rows = parse_table_rows(table_content)
      
      
    -- Обробляємо кожен рядок
     for _, row in ipairs(rows) do
     for _, row in ipairs(rows) do
         if mw.ustring.find(row, player_pattern) then
         if mw.ustring.find(row, player_pattern) then
            -- Розбиваємо рядок на комірки
             local cells = parse_row_cells(row)
             local cells = {}
             if column_index and column_index <= #cells then
           
                 return cells[column_index]
            -- Видаляємо початковий |
            row = mw.ustring.gsub(row, "^%s*|%s*", "")
           
            -- Розбиваємо по ||
            for cell in mw.ustring.gmatch(row, "([^|]+)") do
                -- Пропускаємо порожні комірки від подвійних ||
                local trimmed = mw.text.trim(cell)
                if trimmed ~= "" then
                    table.insert(cells, trimmed)
                end
            end
           
            -- Повертаємо потрібну колонку
             if column_index <= #cells then
                 return clean_wikilinks(cells[column_index])
            else
                return error_output("Column Out of Range",
                    string.format("Requested col %d, found %d cells", column_index, #cells))
             end
             end
            return cells
         end
         end
     end
     end
      
      
     return error_output("Player Not Found", player_name)
     return nil
end
end


----------------------------------------------------------------------
-- ================================================
-- ФУНКЦІЇ, ЩО ВИКЛИКАЮТЬСЯ З ШАБЛОНУ
-- FETCHDATA (season_result)
----------------------------------------------------------------------
-- ================================================
function p.recruiter(frame)
 
     local name = frame.args.player
function p.season_result(frame)
     -- Don't clean wikilinks - keep them for clickable links
    local season = frame.args.season
     local title = mw.title.new("Гравці")
    local player = frame.args.player
     if not title or not title.exists then  
   
        return error_output("Page Missing", "Гравці")
    local season_titles = {
    end
        "Перший сезон", "Другий сезон", "Третій сезон",
        "Четвертий сезон", "П'ятий сезон", "Шостий сезон",
        "Сьомий сезон", "Восьмий сезон", "Дев'ятий сезон"
    }
   
    local season_title = season_titles[tonumber(season)]
    if not season_title then return "??" end
   
    local content = get_page_content(season_title)
    if not content then return "??" end
   
    local rating_section = mw.ustring.match(content, "==%s*Рейтинг%s*==.-{|%s*class%s*=%s*\"wikitable sortable\"(.-)|}")
    if not rating_section then return "--" end
   
    local pattern = "|%s*(%d+)%s*|%s*%[%[" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%]%]"
    local direct_pattern = "|%s*(%d+)%s*|%s*" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%s*"
   
    local rank = mw.ustring.match(rating_section, pattern) or mw.ustring.match(rating_section, direct_pattern)
   
    return rank or "--"
end
 
-- ================================================
-- FETCHDATA2 (базова статистика)
-- ================================================
 
function p.games_count(frame)
     local player_name = frame.args.player
     if not player_name or player_name == "" then return "0" end
   
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 2)
    return result or "0"
end
 
function p.wins_count(frame)
    local player_name = frame.args.player
     if not player_name or player_name == "" then return "0" end
      
      
     local content = title:getContent()
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 3)
     if not content then  
    return result or "0"
        return error_output("Content Read Fail", "Гравці")
end
    end
 
function p.losses_count(frame)
    local player_name = frame.args.player
     if not player_name or player_name == "" then return "0" end
      
      
     local table_start = mw.ustring.find(content, "{|")
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 4)
     local table_end = mw.ustring.find(content, "|}", table_start)
    return result or "0"
end
 
function p.win_rate_colored(frame)
     local player_name = frame.args.player
    if not player_name or player_name == "" then return "0%" end
      
      
     if not table_start or not table_end then
     local win_rate = find_player_in_table(get_table_from_page("Статистика"), player_name, 5)
        return error_output("Table Missing", "Гравці")
     if not win_rate then return "0%" end
     end
      
      
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     local rate_num = tonumber(mw.ustring.match(win_rate, "([%d%.]+)"))
    local escaped = mw.ustring.gsub(name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     if not rate_num then return win_rate end
     local player_pattern = "%[%[" .. escaped .. "%]%]"
      
      
     local rows = {}
     local color = "#4caf50"
     for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
     if rate_num < 40 then
         table.insert(rows, row)
        color = "indianred"
    elseif rate_num < 50 then
         color = "orange"
     end
     end
      
      
     local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
     if not mw.ustring.find(win_rate, "%%") then
    if last_row then
         win_rate = win_rate .. "%"
         table.insert(rows, last_row)
     end
     end
      
      
     for _, row in ipairs(rows) do
     return string.format('<span style="color:%s;">%s</span>', color, win_rate)
        if mw.ustring.find(row, player_pattern) then
end
            local cells = {}
 
            row = mw.ustring.gsub(row, "^%s*|%s*", "")
function p.recruiter(frame)
           
    local player_name = frame.args.player
            for cell in mw.ustring.gmatch(row, "([^|]+)") do
    if not player_name or player_name == "" then return "Не вказано" end
                local trimmed = mw.text.trim(cell)
   
                if trimmed ~= "" then
    local table_content = get_table_from_page("Гравці")
                    table.insert(cells, trimmed)
    if not table_content then return "Не вказано" end
                end
   
            end
    local cells = find_player_in_table(table_content, player_name)
           
    if not cells or #cells < 2 then return "Не вказано" end
            if 4 <= #cells then
   
                local raw = mw.text.trim(cells[4])
    local raw = mw.text.trim(cells[2])
                if raw == "Відсутній" or raw == "-" then  
    if raw == "Відсутній" or raw == "-" or raw == "" then  
                    return "Не вказано"  
        return "Не вказано"  
                end
                -- Return with wikilinks intact
                return raw
            end
        end
     end
     end
      
      
     return "Не вказано"
     return raw
end
end


function p.date_added(frame)
function p.date_added(frame)
     local name = frame.args.player
     local player_name = frame.args.player
     local raw = fetch_from_table("Гравці", name, 3)
     if not player_name or player_name == "" then return "Лише Бог знає" end
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
     local raw = find_player_in_table(get_table_from_page("Гравці"), player_name, 3)
        return "Лише Бог знає"
    end
      
      
     if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
     if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
Рядок 169: Рядок 234:
     end
     end
      
      
    -- Парсимо дату у форматі DD.MM.YYYY
     local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")
     local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")
      
      
     if day and month and year then
     if day and month and year then
        -- Кінцева дата: 26.10.2024
         local end_date = os.time({year=2024, month=12, day=1})
         local end_date = os.time({year=2024, month=10, day=26})
         local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
         local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
       
         local days_diff = math.floor((end_date - start_date) / 86400)
         local days_diff = math.floor((end_date - start_date) / 86400)
          
          
Рядок 183: Рядок 245:
      
      
     return "Лише Бог знає"
     return "Лише Бог знає"
end
function p.foundation(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0₴" end
   
    local raw = find_player_in_table(get_table_from_page("Фундація"), player_name, 2)
   
    if not raw or raw == "" or raw == "-" then return "0₴" end
   
    raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)
   
    return (raw or "0") .. " ₴"
end
end


function p.prize_pool(frame)
function p.prize_pool(frame)
     local name = frame.args.player
     local player_name = frame.args.player
     local raw = fetch_from_table("Призовий_фонд", name, 2)  
    if not player_name or player_name == "" then return "0₴" end
   
     local raw = find_player_in_table(get_table_from_page("Призовий_фонд"), player_name, 2)
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then  
     if not raw or raw == "" or raw == "-" then return "0₴" end
        return "0 ₴"
    end
      
      
     if raw then
     raw = mw.ustring.gsub(raw, "[^%d%s]", "")
        raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)
        raw = mw.text.trim(raw)
    end
      
      
     return (raw or "0") .. " ₴"
     return (raw or "0") .. " ₴"
end
end


-- Додай цю функцію до FetchData2 (модуль:FetchData2)
function p.finalist(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0/9" end
   
    local raw = find_player_in_table(get_table_from_page("Фіналіст"), player_name, 2)
   
    if not raw or raw == "" or raw == "-" then return "0/9" end
   
    local number_only = mw.ustring.gsub(raw, "[^%d]", "")
    local count = tonumber(number_only)
   
    if count then
        return string.format("%d/9", count)
    end
   
    return "0/9"
end


function p.foty_rating(frame)
function p.foty_rating(frame)
     local name = frame.args.player
     local player_name = frame.args.player
    if not player_name or player_name == "" then return "—" end
   
    local content = get_page_content("Період")
    if not content then return "—" end
      
      
     if not name or name == "" then
     local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
         return "0"
    if not foty_section then
         foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
     end
     end
      
      
    local title = mw.title.new("Період")
     if not foty_section then return "" end
     if not title or not title.exists then  
        return "0"
    end
      
      
     local content = title:getContent()
     local table_start = mw.ustring.find(foty_section, "{|")
     if not content then
     local table_end = mw.ustring.find(foty_section, "|}", table_start)
        return "0"
    end
      
      
    -- Шукаємо секцію "Фінал Року"
     if not table_start or not table_end then return "" end
    local section_start = mw.ustring.find(content, "== Фінал Року ==")
     if not section_start then
        return "0"
    end
      
      
    -- Знаходимо таблицю після секції
     local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
     local table_start = mw.ustring.find(content, "{|", section_start)
     local cells = find_player_in_table(table_content, player_name)
     if not table_start then
        return "0"
    end
      
      
    local table_end = mw.ustring.find(content, "|}", table_start)
     if not cells or #cells < 3 then return "" end
     if not table_end then
        return "0"
    end
      
      
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     local place = strip_html_tags(cells[1]) or "—"
    local rating = strip_html_tags(cells[3]) or "—"
      
      
     -- Екрануємо спеціальні символи в імені гравця
     rating = mw.ustring.gsub(rating, "[^%d%-]", "")
    local escaped = mw.ustring.gsub(name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     rating = mw.text.trim(rating)
     local player_pattern = "%[%[" .. escaped .. "%]%]"
      
      
     -- Збираємо всі рядки
     if rating and rating ~= "" then
    local rows = {}
        return rating .. " (" .. place .. " місце)"
    for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
        table.insert(rows, row)
     end
     end
      
      
     local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
     return "—"
     if last_row then
end
         table.insert(rows, last_row)
 
-- ================================================
-- FETCHDATA3 (титули та нагороди)
-- ================================================
 
local tournament_dates = {
    ["Women's Closed Cup I"] = "15.03.2023",
    ["Men's Closed Cup I"] = "05.04.2023",
    ["Combined Closed Cup I"] = "21.04.2023",
    ["Score Open Battle I"] = "07.05.2023",
    ["Women's Closed Cup II"] = "11.05.2023",
    ["Men's Closed Cup II"] = "27.05.2023",
    ["Combined Closed Cup II"] = "02.06.2023",
    ["Score Open Battle II"] = "17.06.2023",
    ["Mafia Closed Cup I"] = "12.11.2023",
    ["Mafia Closed Cup I Online"] = "28.01.2024",
    ["My Closest Circle I"] = "01.01.2023",
    ["Get Names 01"] = "08.10.2023",
    ["Get Names 02"] = "03.12.2023",
    ["Get Names 03"] = "10.03.2024",
    ["Get Names 04"] = "27.04.2024",
    ["Get Names 05"] = "14.07.2024",
    ["Get Names 06"] = "21.07.2024",
    ["Get Names 07"] = "08.09.2024",
    ["Get Names 08"] = "19.09.2024",
    ["Get Names 09"] = "29.09.2024",
    ["Перший сезон (фінал)"] = "09.09.2023",
    ["Перший сезон (рейтинг)"] = "09.09.2023",
    ["Другий сезон (фінал)"] = "28.10.2023",
    ["Другий сезон (рейтинг)"] = "28.10.2023",
    ["Третій сезон (фінал)"] = "23.12.2023",
    ["Третій сезон (рейтинг)"] = "23.12.2023",
    ["Четвертий сезон (фінал)"] = "10.02.2024",
    ["Четвертий сезон (рейтинг)"] = "10.02.2024",
    ["П'ятий сезон (фінал)"] = "06.04.2024",
    ["П'ятий сезон (рейтинг)"] = "06.04.2024",
    ["Шостий сезон (фінал)"] = "11.05.2024",
    ["Шостий сезон (рейтинг)"] = "11.05.2024",
    ["Сьомий сезон (фінал)"] = "06.07.2024",
    ["Сьомий сезон (рейтинг)"] = "06.07.2024",
    ["Восьмий сезон (фінал)"] = "10.08.2024",
    ["Восьмий сезон (рейтинг)"] = "10.08.2024",
    ["Дев'ятий сезон (фінал)"] = "28.09.2024",
    ["Дев'ятий сезон (рейтинг)"] = "28.09.2024",
    ["Фінал Року"] = "13.10.2024"
}
 
local function get_medal_icon(value)
    if not value or value == "" or value == " " then return nil end
    value = mw.text.trim(value)
   
    if value == "" then return nil
    elseif value == "1" then return "[[Файл:Gold.png|20px|link=]]"
    elseif value == "2" then return "[[Файл:Silver.png|20px|link=]]"
    elseif value == "3" then return "[[Файл:Bronze.png|20px|link=]]"
    elseif value == "4" then return "[[Файл:Finalist.png|20px|link=]]"
    elseif value == "S" or mw.ustring.match(value, "^S%d") then return "[[Файл:Star.png|20px|link=]]"
    else return nil
    end
end
 
local function to_roman(num)
    local romans = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
    return romans[tonumber(num)] or num
end
 
local function get_tournament_link(header_name)
    local season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(фінал%)")
     if not season_num then
         season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(рейтинг%)")
     end
     end
      
      
     -- Обробляємо кожен рядок
     if season_num then
    for _, row in ipairs(rows) do
        local season_map = {
        if mw.ustring.find(row, player_pattern) then
            ["Перший"] = 1, ["Другий"] = 2, ["Третій"] = 3,
             local cells = {}
            ["Четвертий"] = 4, ["П'ятий"] = 5, ["Шостий"] = 6,
             row = mw.ustring.gsub(row, "^%s*|%s*", "")
            ["Сьомий"] = 7, ["Восьмий"] = 8, ["Дев'ятий"] = 9
        }
       
        local season_number = season_map[season_num]
        if season_number then
            local roman = to_roman(season_number)
            local type_text = mw.ustring.find(header_name, "фінал") and "Фінал." or "Рейтинг."
             local clean_name = mw.ustring.gsub(header_name, "%s*%(фінал%)%s*", "")
             clean_name = mw.ustring.gsub(clean_name, "%s*%(рейтинг%)%s*", "")
            clean_name = mw.text.trim(clean_name)
              
              
             for cell in mw.ustring.gmatch(row, "([^|]+)") do
             return string.format("[[%s|%s сезон. %s]]", clean_name, roman, type_text)
                local trimmed = mw.text.trim(cell)
        end
                if trimmed ~= "" then
    end
                    table.insert(cells, trimmed)
   
                end
    return string.format("[[%s]]", header_name)
end
 
local function get_player_row(page_title, player_name)
    local table_content = get_table_from_page(page_title)
    if not table_content then return nil end
   
    local first_row = mw.ustring.match(table_content, "{|[^\n]*\n!(.-)[\n]")
    local headers = {}
   
    if first_row then
        for header in mw.ustring.gmatch(first_row, "([^|]+)") do
            local trimmed = mw.text.trim(header)
            if trimmed ~= "" then
                table.insert(headers, trimmed)
             end
             end
           
            -- Колонка 1 - це місце (№)
            -- Колонка 3 - це рейтинг (Σ)
            if #cells >= 3 then
                local place = mw.text.trim(cells[1])
                local rating = cells[3]
               
                -- Видаляємо HTML теги з рейтингу
                rating = mw.ustring.gsub(rating, "<span[^>]*>", "")
                rating = mw.ustring.gsub(rating, "</span>", "")
               
                -- Видаляємо всі нецифрові символи окрім мінуса
                rating = mw.ustring.gsub(rating, "[^%d%-]", "")
                rating = mw.text.trim(rating)
               
                if rating and rating ~= "" then
                    return string.format("%s (%s місце)", rating, place)
                end
            end
           
            return "0"
         end
         end
     end
     end
      
      
     return "0"
    if #headers == 0 then return nil end
   
    local cells = find_player_in_table(table_content, player_name)
    if not cells then return nil end
   
     return {headers = headers, cells = cells}
end
end


function p.foundation(frame)
-- ================================================
     local name = frame.args.player
-- PLAYER IMAGE (автовибір аватара)
     local raw = fetch_from_table("Фундація", name, 3)
-- ================================================
 
function p.player_image(frame)
     local nickname = frame.args.nickname or frame.args[1] or mw.title.getCurrentTitle().text
     local player_file = "Player_" .. nickname .. ".png"
    local placeholder = "MCC_Placeholder.png"
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
     local title = mw.title.new("File:" .. player_file)
        return "0 ₴"
    end
      
      
     if raw then  
    local file_to_use
         raw = mw.ustring.gsub(raw, "[^%d%s]", "")
     if title and title.exists then
         raw = mw.text.trim(raw)
         file_to_use = player_file
    else
         file_to_use = placeholder
     end
     end
      
      
     return (raw or "0") .. " ₴"
     return string.format("[[File:%s|none|alt=Фотографія гравця|center|360px]]", file_to_use)
end
end


function p.finalist(frame)
function p.titles(frame)
     local name = frame.args.player
     local name = frame.args.player
     local raw = fetch_from_table("Фіналіст", name, 3)
     if not name or name == "" then return "''Відсутні''" end
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then  
     local titles_data = get_player_row("Титули", name)
        return "0/9"
    if not titles_data then return "''Відсутні''" end
     end
   
    local prize_data = get_player_row("Призові", name)
    if not prize_data then return error_output("Prize Data Missing", name) end
   
     local results = {}
      
      
     if not raw or raw == "" or raw == "-" then
     for i = 2, #titles_data.cells do
        return "0/9"
        local cell_value = titles_data.cells[i]
        local header = titles_data.headers[i]
        local prize_value = prize_data.cells[i]
       
        if cell_value and cell_value ~= "" and cell_value ~= "-" and header then
            local medal = get_medal_icon(cell_value)
           
            if medal then
                local prize_amount = "0 ₴"
               
                if prize_value and prize_value ~= "" and prize_value ~= "-" then
                    prize_value = mw.ustring.gsub(prize_value, "₴", "")
                    prize_value = mw.text.trim(prize_value)
                    if prize_value ~= "0" then
                        prize_amount = prize_value .. " ₴"
                    end
                end
               
                local date = tournament_dates[header] or "01.01.2023"
                local tournament_link = get_tournament_link(header)
               
                table.insert(results, {
                    date = date,
                    medal = medal,
                    tournament = tournament_link,
                    prize = prize_amount
                })
            end
        end
     end
     end
      
      
     -- Видаляємо всі нецифрові символи
     if #results == 0 then return "''Відсутні''" end
    local number_only = mw.ustring.gsub(raw, "[^%d]", "")
      
      
     -- Перетворюємо в число
     local table_html = {
     local count = tonumber(number_only)
        '{| class="wikitable sortable" style="font-size: 14.5px;"',
        '|-',
        '! style="" | Дата',
        '! style="text-align: left;" | Місце і турнір',
        '! style="text-align: left;" | Приз'
     }
      
      
     if count then
     for _, result in ipairs(results) do
         return string.format("%d/9", count)
        table.insert(table_html, '|-')
         table.insert(table_html, string.format(
            '| style="width:80px; text-align:center;" | %s || style="text-align:left; padding-left:10px;" | %s %s || style="text-align:right;" | %s',
            result.date, result.medal, result.tournament, result.prize
        ))
     end
     end
      
      
     return "0/9"
    table.insert(table_html, '|}')
     return table.concat(table_html, '\n')
end
end


function p.games_count(frame)
-- ================================================
     local name = frame.args.player
-- FETCHDATA4 (цікаві факти)
     local raw = fetch_from_table("Статистика", name, 3)
-- ================================================
 
local pages_to_search = {
    "Перший сезон", "Другий сезон", "Третій сезон", "Четвертий сезон",
    "П'ятий сезон", "Шостий сезон", "Сьомий сезон", "Восьмий сезон",
    "Дев'ятий сезон", "Mafia Closed Circle", "Ten's Games", "Alternatywa",
    "Майстерня Стратегічного Саморозвитку", "Get Names 01", "Get Names 02",
    "Get Names 03", "Get Names 04", "Get Names 05", "Get Names 06",
    "Get Names 07", "Get Names 08", "Get Names 09", "Women's Closed Cup I",
    "Women's Closed Cup II", "Men's Closed Cup I", "Men's Closed Cup II",
    "Combined Closed Cup I", "Combined Closed Cup II", "Score Open Battle I",
    "Score Open Battle II", "Mafia Closed Cup I", "Mafia Closed Cup I Online",
    "My Closest Circle I", "Фінал Року"
}
 
local function get_facts_section(page_title)
    local content = get_page_content(page_title)
    if not content then return nil end
   
     local section_start = mw.ustring.find(content, "==%s*[Цц]ікаві%s*[Фф]акти%s*==")
    if not section_start then return nil end
   
     local section_end = mw.ustring.find(content, "\n==[^=]", section_start + 1)
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
    local section_content
        return "0"
     if section_end then
        section_content = mw.ustring.sub(content, section_start, section_end - 1)
    else
        section_content = mw.ustring.sub(content, section_start)
     end
     end
      
      
     return raw or "0"
     return section_content
end
end


function p.wins_count(frame)
local function fact_contains_player(fact, player_name)
     local name = frame.args.player
    local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local raw = fetch_from_table("Статистика", name, 4)
     local pattern1 = "%[%[" .. escaped_name .. "%]%]"
     local pattern2 = "%[%[" .. escaped_name .. "|[^%]]+%]%]"
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
     return mw.ustring.find(fact, pattern1) or mw.ustring.find(fact, pattern2)
         return "0"
end
 
local function extract_facts(section_content)
    local facts = {}
    for fact in mw.ustring.gmatch(section_content, "%*[^\n]+") do
         table.insert(facts, fact)
     end
     end
   
     return facts
     return raw or "0"
end
end


function p.losses_count(frame)
function p.facts(frame)
     local name = frame.args.player
     local player_name = frame.args.player or frame.args[1] or mw.title.getCurrentTitle().text
     local raw = fetch_from_table("Статистика", name, 5)
   
     local result_parts = {}
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
     for _, page_title in ipairs(pages_to_search) do
         return "0"
        local section = get_facts_section(page_title)
       
        if section then
            local facts = extract_facts(section)
            local player_facts = {}
           
            for _, fact in ipairs(facts) do
                if fact_contains_player(fact, player_name) then
                    table.insert(player_facts, fact)
                end
            end
           
            if #player_facts > 0 then
                local block = string.format("==== %s ====\n", page_title)
                for _, fact in ipairs(player_facts) do
                    block = block .. fact .. "\n"
                end
                table.insert(result_parts, string.format('<div style="margin-bottom: 20px;">\n%s</div>', block))
            end
         end
     end
     end
      
      
     return raw or "0"
     if #result_parts == 0 then return "" end
   
    return table.concat(result_parts, "\n")
end
end


function p.win_rate(frame)
-- ================================================
     local name = frame.args.player
-- FETCHDATA5 (записи ігор) - ВИПРАВЛЕНО
     local raw = fetch_from_table("Статистика", name, 6)
-- ================================================
 
local function get_tournament_link_games(short_name, full_name)
     if not short_name or short_name == "" then
        return "Невідомо"
     end
   
    if short_name == "Фанова гра" then
        return "Фанова гра"
    end
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
    -- Очищаємо full_name від [[ ]]
        return "0%"
     if full_name then
        full_name = clean_wikilinks(full_name)
     end
     end
      
      
     if raw then
     if full_name and full_name ~= "" and full_name ~= short_name then
         -- Якщо немає знаку %, додаємо його
         return "[[" .. full_name .. "|" .. short_name .. "]]"
         if not mw.ustring.find(raw, "%%") then
    else
             return raw .. "%"
        return "[[" .. short_name .. "]]"
    end
end
 
local function parse_games_table_rows(table_content)
    local rows = {}
   
    -- Шукаємо рядки після |- (inline формат: все в одному рядку)
    for row in mw.ustring.gmatch(table_content, "|-\n([^\n]+)") do
        -- Пропускаємо заголовки (починаються з !)
         if not mw.ustring.match(row, "^%s*!") then
             table.insert(rows, row)
         end
         end
        return raw
     end
     end
      
      
     return "0%"
     return rows
end
end


function p.win_rate_colored(frame)
local function parse_games_row_cells(row)
     local name = frame.args.player
     local cells = {}
     local raw = fetch_from_table("Статистика", name, 6)
   
    -- Видаляємо початковий |
     row = mw.ustring.gsub(row, "^%s*|%s*", "")
      
      
     if type(raw) == "string" and mw.ustring.find(raw, "Error") then
     -- Розділяємо по ||
         return "0%"
    for cell in mw.ustring.gmatch(row .. "||", "(.-)%s*||%s*") do
         cell = mw.text.trim(cell)
        table.insert(cells, cell)
     end
     end
      
      
     if not raw or raw == "" then
     return cells
        return "0%"
end
 
local function get_player_role(player_name, cells)
    -- Колонки 6-11: мирні
    for i = 6, 11 do
        if cells[i] and clean_wikilinks(cells[i]) == player_name then
            return "Мирний"
        end
     end
     end
      
      
     -- Видаляємо % та конвертуємо в число
     -- Колонка 12: шериф
     local percent_str = mw.ustring.gsub(raw, "%%", "")
     if cells[12] and clean_wikilinks(cells[12]) == player_name then
    local percent_num = tonumber(percent_str)
         return "Шериф"
   
    if not percent_num then
         return raw
     end
     end
      
      
     -- Визначаємо колір
     -- Колонки 13-14: мафія
     local color = ""
     for i = 13, 14 do
    if percent_num < 50 then
        if cells[i] and clean_wikilinks(cells[i]) == player_name then
        color = "indianred"
            return "Мафія"
    elseif percent_num > 50 then
         end
        color = "gold"
    else
        -- Рівно 50% - білий
         color = "white"
     end
     end
      
      
     -- Додаємо % якщо його немає
     -- Колонка 15: дон
    local display_text = raw
     if cells[15] and clean_wikilinks(cells[15]) == player_name then
     if not mw.ustring.find(raw, "%%") then
         return "Дон"
         display_text = raw .. "%"
     end
     end
      
      
     return string.format("<span style='color:%s;'>%s</span>", color, display_text)
     return "Невідомо"
end
end


--- Отримує всіх гравців з таблиці "Гравці" в порядку дати приєднання
local function get_player_result(role, game_result)
local function get_all_players()
     if not game_result or game_result == "" then return "Невідомо" end
     local title = mw.title.new("Гравці")
   
     if not title or not title.exists then  
    game_result = mw.text.trim(game_result)
         return nil
   
     if game_result == "Місто" or game_result == "Мирні" then
         return (role == "Мирний" or role == "Шериф") and "Перемога" or "Поразка"
     end
     end
      
      
     local content = title:getContent()
     if game_result == "Мафія" then
    if not content then  
         return (role == "Мафія" or role == "Дон") and "Перемога" or "Поразка"
         return nil
     end
     end
      
      
     local table_start = mw.ustring.find(content, "{|")
    return "Невідомо"
     local table_end = mw.ustring.find(content, "|}", table_start)
end
 
local function get_player_games_list(player_name)
     local table_content = get_table_from_page("Ігри")
    if not table_content then return nil end
   
     local rows = parse_games_table_rows(table_content)
    local games = {}
      
      
     if not table_start or not table_end then
     for _, row in ipairs(rows) do
         return nil
        local cells = parse_games_row_cells(row)
       
        if #cells >= 17 then
            -- Перевіряємо чи гравець у грі (колонки 6-15)
            local player_in_game = false
            for i = 6, 15 do
                if cells[i] and clean_wikilinks(cells[i]) == player_name then
                    player_in_game = true
                    break
                end
            end
           
            if player_in_game then
                local role = get_player_role(player_name, cells)
                local result = get_player_result(role, cells[16])
               
                table.insert(games, {
                    short = cells[3] or "",
                    tournament = cells[4] or "",
                    time = cells[5] or "",
                    result = result,
                    link = cells[17] or ""
                })
            end
         end
     end
     end
      
      
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
    return games
end
 
function p.player_games(frame)
     local player_name = frame.args.player
    if not player_name or player_name == "" then
        return error_output("No Player Name", "")
    end
      
      
     local players = {}
     local games = get_player_games_list(player_name)
     local rows = {}
     if not games then
    for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
        return error_output("Cannot Load Games", "Ігри")
        table.insert(rows, row)
     end
     end
      
      
     local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
     if #games == 0 then
    if last_row then
         return "''Ігор не знайдено''"
         table.insert(rows, last_row)
     end
     end
      
      
     for _, row in ipairs(rows) do
    local htmlTable = mw.html.create('table')
         local cells = {}
        :addClass('wikitable sortable')
         row = mw.ustring.gsub(row, "^%s*|%s*", "")
        :css('width', '100%')
        :css('font-size', '14.5px')
   
    local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Турнір')
    headerRow:tag('th'):wikitext('Час')
    headerRow:tag('th'):wikitext('Результат')
    headerRow:tag('th'):wikitext('Запис')
   
     for _, game in ipairs(games) do
         local row = htmlTable:tag('tr')
       
        -- Турнір (скорочення з посиланням)
         row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(get_tournament_link_games(game.short, game.tournament))
       
        -- Час
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(game.time)
       
        -- Результат (з кольором)
        local resultCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
          
          
         for cell in mw.ustring.gmatch(row, "([^|]+)") do
         if game.result == "Перемога" then
            local trimmed = mw.text.trim(cell)
            resultCell:tag('span')
            if trimmed ~= "" then
                :css('color', '#4caf50')
                 table.insert(cells, trimmed)
                :wikitext('Перемога')
             end
        elseif game.result == "Поразка" then
            resultCell:tag('span')
                 :css('color', 'indianred')
                :wikitext('Поразка')
        else
             resultCell:wikitext(game.result)
         end
         end
          
          
         -- Колонка 2 - ім'я гравця
         -- Кнопка перегляду
         if #cells >= 2 then
         local linkCell = row:tag('td')
             local player_name = clean_wikilinks(cells[2])
             :css('text-align', 'center')
             if player_name then
             :css('padding', '8px')
                table.insert(players, player_name)
       
            end
        if game.link and game.link ~= "" then
            linkCell:wikitext('<span class="game-record-btn">[' .. game.link .. ' ▶︎]</span>')
         end
         end
     end
     end
      
      
     return players
     return tostring(htmlTable)
end
 
function p.games_count_records(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
   
    local games = get_player_games_list(player_name)
    return games and tostring(#games) or "0"
end
end


function p.prev_player(frame)
-- ================================================
     local name = frame.args.player
-- FETCHDATA6 (досягнення в сезонах)
     local players = get_all_players()
-- ================================================
 
local function get_season_data(season_number, player_name)
     local season_names = {
        [1] = "Перший сезон", [2] = "Другий сезон", [3] = "Третій сезон",
        [4] = "Четвертий сезон", [5] = "П'ятий сезон", [6] = "Шостий сезон",
        [7] = "Сьомий сезон", [8] = "Восьмий сезон", [9] = "Дев'ятий сезон"
    }
   
    local season_name = season_names[season_number]
    if not season_name then return nil end
   
     local content = get_page_content(season_name)
    if not content then return nil end
      
      
     if not players then
    local rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.-)\n==")
         return ""
     if not rating_section then
         rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.*)")
     end
     end
    if not rating_section then return nil end
      
      
     for i, player in ipairs(players) do
    local table_start = mw.ustring.find(rating_section, "{|")
         if player == name then
    local table_end = mw.ustring.find(rating_section, "|}", table_start)
             if i > 1 then
    if not table_start or not table_end then return nil end
                 return players[i - 1]
   
            else
    local table_content = mw.ustring.sub(rating_section, table_start, table_end + 1)
                return "" -- Перший гравець, немає попереднього
   
    local current_row = {}
    local all_rows = {}
   
     for line in mw.ustring.gmatch(table_content .. "\n", "([^\n]*)\n") do
        line = mw.text.trim(line)
       
         if line == "|-" then
             if #current_row > 0 then
                 table.insert(all_rows, current_row)
             end
             end
            current_row = {}
        elseif mw.ustring.match(line, "^|[^-}]") then
            local cell_content = mw.ustring.gsub(line, "^|%s*", "")
            table.insert(current_row, cell_content)
         end
         end
     end
     end
      
      
     return ""
     if #current_row > 0 then
end
        table.insert(all_rows, current_row)
 
function p.next_player(frame)
    local name = frame.args.player
    local players = get_all_players()
   
    if not players then
        return ""
     end
     end
      
      
     for i, player in ipairs(players) do
     for _, row in ipairs(all_rows) do
         if player == name then
         if #row >= 5 then
             if i < #players then
            local player_cell = clean_wikilinks(row[2])
                 return players[i + 1]
           
            else
             if player_cell == player_name then
                 return "" -- Останній гравець, немає наступного
                 return {
                    place = mw.text.trim(row[1]),
                    points = mw.text.trim(row[3]),
                    games = mw.text.trim(row[4]),
                    winrate = mw.text.trim(row[5])
                 }
             end
             end
         end
         end
     end
     end
      
      
     return ""
     return nil
end
end


function p.prev_player_link(frame)
function p.season_achievements(frame)
     local name = frame.args.player
     local player_name = frame.args.player
     local prev = p.prev_player(frame)
    if not player_name or player_name == "" then
        return "<span style='color:red;'>Ім'я гравця не вказано</span>"
    end
   
     local htmlTable = mw.html.create('table')
        :addClass('wikitable sortable')
        :css('width', '100%')
        :css('font-size', '14.5px')
      
      
     if prev == "" or not prev then
     local headerRow = htmlTable:tag('tr')
        return ""
    headerRow:tag('th'):wikitext('Сезон')
     end
    headerRow:tag('th'):wikitext('Місце')
    headerRow:tag('th'):wikitext('Бали')
    headerRow:tag('th'):wikitext('% перемог')
     headerRow:tag('th'):wikitext('Ігор')
      
      
     return string.format(
     local season_links = {
         '<a href="/index.php/%s" style="width:48px; height:48px; display:flex; align-items:center; justify-content:center; text-decoration:none; border:2px solid #333; border-radius:8px;"><span style="color:gold; font-size:24px; font-weight:bold;">←</span></a>',
         [1] = "[[Перший сезон#Рейтинг|01 сезон]]",
         mw.uri.encode(prev, "WIKI")
        [2] = "[[Другий сезон#Рейтинг|02 сезон]]",
    )
        [3] = "[[Третій сезон#Рейтинг|03 сезон]]",
end
         [4] = "[[Четвертий сезон#Рейтинг|04 сезон]]",
 
        [5] = "[[П'ятий сезон#Рейтинг|05 сезон]]",
function p.next_player_link(frame)
        [6] = "[[Шостий сезон#Рейтинг|06 сезон]]",
    local name = frame.args.player
        [7] = "[[Сьомий сезон#Рейтинг|07 сезон]]",
     local next = p.next_player(frame)
        [8] = "[[Восьмий сезон#Рейтинг|08 сезон]]",
        [9] = "[[Дев'ятий сезон#Рейтинг|09 сезон]]"
     }
      
      
     if next == "" or not next then
     for season = 1, 9 do
         return ""
        local data = get_season_data(season, player_name)
        local row = htmlTable:tag('tr')
       
        row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(season_links[season])
       
        if data then
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.place)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.points)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.winrate)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.games)
        else
            for i = 1, 4 do
                row:tag('td'):css('text-align', 'center'):css('padding', '8px'):css('color', '#666'):wikitext('-')
            end
         end
     end
     end
      
      
     return string.format(
     return tostring(htmlTable)
        '<a href="/index.php/%s" style="width:48px; height:48px; display:flex; align-items:center; justify-content:center; text-decoration:none; border:2px solid #333; border-radius:8px;"><span style="color:gold; font-size:24px; font-weight:bold;">→</span></a>',
        mw.uri.encode(next, "WIKI")
    )
end
end


return p
return p

Поточна версія на 08:49, 3 грудня 2025

Документацію для цього модуля можна створити у Модуль:FetchData/документація

local p = {}

-- ================================================
-- КЕШУВАННЯ (найважливіша оптимізація)
-- ================================================

local page_cache = {}
local table_cache = {}

local function get_page_content(page_name)
    if page_cache[page_name] then
        return page_cache[page_name]
    end
    
    local title = mw.title.new(page_name)
    if not title or not title.exists then
        page_cache[page_name] = nil
        return nil
    end
    
    local content = title:getContent()
    page_cache[page_name] = content
    return content
end

local function get_table_from_page(page_name)
    if table_cache[page_name] then
        return table_cache[page_name]
    end
    
    local content = get_page_content(page_name)
    if not content then 
        table_cache[page_name] = nil
        return nil 
    end
    
    local table_start = mw.ustring.find(content, "{|")
    local table_end = mw.ustring.find(content, "|}", table_start)
    
    if not table_start or not table_end then
        table_cache[page_name] = nil
        return nil
    end
    
    local table_content = mw.ustring.sub(content, table_start, table_end + 1)
    table_cache[page_name] = table_content
    return table_content
end

-- ================================================
-- УТИЛІТИ (спільні для всіх функцій)
-- ================================================

local function error_output(context, error_details)
    local safe_details = error_details or "N/A"
    return string.format("<span style='color:red; font-weight:bold;'>[Error: %s. Details: %s]</span>", 
        context or "Unknown", safe_details)
end

local function clean_wikilinks(text)
    if not text then return nil end
    text = mw.ustring.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    text = mw.ustring.gsub(text, "%[%[([^%]]+)%]%]", "%1")
    return mw.text.trim(text)
end

local function strip_html_tags(text)
    if not text then return text end
    text = mw.ustring.gsub(text, "<span[^>]*>", "")
    text = mw.ustring.gsub(text, "</span>", "")
    return mw.text.trim(text)
end

local function parse_table_rows(table_content)
    local rows = {}
    for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
        table.insert(rows, row)
    end
    local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
    if last_row then
        table.insert(rows, last_row)
    end
    return rows
end

local function parse_row_cells(row)
    local cells = {}
    
    for line in mw.ustring.gmatch(row, "[^\n]+") do
        line = mw.text.trim(line)
        
        if line ~= "|-" and line ~= "" then
            line = mw.ustring.gsub(line, "^|%s*", "")
            
            if line ~= "" then
                table.insert(cells, line)
            end
        end
    end
    
    return cells
end

local function find_player_in_table(table_content, player_name, column_index)
    if not table_content then return nil end
    
    local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local player_pattern = "%[%[" .. escaped .. "%]%]"
    
    local rows = parse_table_rows(table_content)
    
    for _, row in ipairs(rows) do
        if mw.ustring.find(row, player_pattern) then
            local cells = parse_row_cells(row)
            if column_index and column_index <= #cells then
                return cells[column_index]
            end
            return cells
        end
    end
    
    return nil
end

-- ================================================
-- FETCHDATA (season_result)
-- ================================================

function p.season_result(frame)
    local season = frame.args.season
    local player = frame.args.player
    
    local season_titles = {
        "Перший сезон", "Другий сезон", "Третій сезон",
        "Четвертий сезон", "П'ятий сезон", "Шостий сезон", 
        "Сьомий сезон", "Восьмий сезон", "Дев'ятий сезон"
    }
    
    local season_title = season_titles[tonumber(season)]
    if not season_title then return "??" end
    
    local content = get_page_content(season_title)
    if not content then return "??" end
    
    local rating_section = mw.ustring.match(content, "==%s*Рейтинг%s*==.-{|%s*class%s*=%s*\"wikitable sortable\"(.-)|}")
    if not rating_section then return "--" end
    
    local pattern = "|%s*(%d+)%s*|%s*%[%[" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%]%]"
    local direct_pattern = "|%s*(%d+)%s*|%s*" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%s*"
    
    local rank = mw.ustring.match(rating_section, pattern) or mw.ustring.match(rating_section, direct_pattern)
    
    return rank or "--"
end

-- ================================================
-- FETCHDATA2 (базова статистика)
-- ================================================

function p.games_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
    
    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 2)
    return result or "0"
end

function p.wins_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
    
    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 3)
    return result or "0"
end

function p.losses_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
    
    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 4)
    return result or "0"
end

function p.win_rate_colored(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0%" end
    
    local win_rate = find_player_in_table(get_table_from_page("Статистика"), player_name, 5)
    if not win_rate then return "0%" end
    
    local rate_num = tonumber(mw.ustring.match(win_rate, "([%d%.]+)"))
    if not rate_num then return win_rate end
    
    local color = "#4caf50"
    if rate_num < 40 then
        color = "indianred"
    elseif rate_num < 50 then
        color = "orange"
    end
    
    if not mw.ustring.find(win_rate, "%%") then
        win_rate = win_rate .. "%"
    end
    
    return string.format('<span style="color:%s;">%s</span>', color, win_rate)
end

function p.recruiter(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "Не вказано" end
    
    local table_content = get_table_from_page("Гравці")
    if not table_content then return "Не вказано" end
    
    local cells = find_player_in_table(table_content, player_name)
    if not cells or #cells < 2 then return "Не вказано" end
    
    local raw = mw.text.trim(cells[2])
    if raw == "Відсутній" or raw == "-" or raw == "" then 
        return "Не вказано" 
    end
    
    return raw
end

function p.date_added(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "Лише Бог знає" end
    
    local raw = find_player_in_table(get_table_from_page("Гравці"), player_name, 3)
    
    if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
        return "Лише Бог знає"
    end
    
    local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")
    
    if day and month and year then
        local end_date = os.time({year=2024, month=12, day=1})
        local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
        local days_diff = math.floor((end_date - start_date) / 86400)
        
        return string.format("%s (%d днів)", raw, days_diff)
    end
    
    return "Лише Бог знає"
end

function p.foundation(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0₴" end
    
    local raw = find_player_in_table(get_table_from_page("Фундація"), player_name, 2)
    
    if not raw or raw == "" or raw == "-" then return "0₴" end
    
    raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)
    
    return (raw or "0") .. " ₴"
end

function p.prize_pool(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0₴" end
    
    local raw = find_player_in_table(get_table_from_page("Призовий_фонд"), player_name, 2)
    
    if not raw or raw == "" or raw == "-" then return "0₴" end
    
    raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)
    
    return (raw or "0") .. " ₴"
end

function p.finalist(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0/9" end
    
    local raw = find_player_in_table(get_table_from_page("Фіналіст"), player_name, 2)
    
    if not raw or raw == "" or raw == "-" then return "0/9" end
    
    local number_only = mw.ustring.gsub(raw, "[^%d]", "")
    local count = tonumber(number_only)
    
    if count then
        return string.format("%d/9", count)
    end
    
    return "0/9"
end

function p.foty_rating(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "—" end
    
    local content = get_page_content("Період")
    if not content then return "—" end
    
    local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
    if not foty_section then
        foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
    end
    
    if not foty_section then return "—" end
    
    local table_start = mw.ustring.find(foty_section, "{|")
    local table_end = mw.ustring.find(foty_section, "|}", table_start)
    
    if not table_start or not table_end then return "—" end
    
    local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
    local cells = find_player_in_table(table_content, player_name)
    
    if not cells or #cells < 3 then return "—" end
    
    local place = strip_html_tags(cells[1]) or "—"
    local rating = strip_html_tags(cells[3]) or "—"
    
    rating = mw.ustring.gsub(rating, "[^%d%-]", "")
    rating = mw.text.trim(rating)
    
    if rating and rating ~= "" then
        return rating .. " (" .. place .. " місце)"
    end
    
    return "—"
end

-- ================================================
-- FETCHDATA3 (титули та нагороди)
-- ================================================

local tournament_dates = {
    ["Women's Closed Cup I"] = "15.03.2023",
    ["Men's Closed Cup I"] = "05.04.2023",
    ["Combined Closed Cup I"] = "21.04.2023",
    ["Score Open Battle I"] = "07.05.2023",
    ["Women's Closed Cup II"] = "11.05.2023",
    ["Men's Closed Cup II"] = "27.05.2023",
    ["Combined Closed Cup II"] = "02.06.2023",
    ["Score Open Battle II"] = "17.06.2023",
    ["Mafia Closed Cup I"] = "12.11.2023",
    ["Mafia Closed Cup I Online"] = "28.01.2024",
    ["My Closest Circle I"] = "01.01.2023",
    ["Get Names 01"] = "08.10.2023",
    ["Get Names 02"] = "03.12.2023",
    ["Get Names 03"] = "10.03.2024",
    ["Get Names 04"] = "27.04.2024",
    ["Get Names 05"] = "14.07.2024",
    ["Get Names 06"] = "21.07.2024",
    ["Get Names 07"] = "08.09.2024",
    ["Get Names 08"] = "19.09.2024",
    ["Get Names 09"] = "29.09.2024",
    ["Перший сезон (фінал)"] = "09.09.2023",
    ["Перший сезон (рейтинг)"] = "09.09.2023",
    ["Другий сезон (фінал)"] = "28.10.2023",
    ["Другий сезон (рейтинг)"] = "28.10.2023",
    ["Третій сезон (фінал)"] = "23.12.2023",
    ["Третій сезон (рейтинг)"] = "23.12.2023",
    ["Четвертий сезон (фінал)"] = "10.02.2024",
    ["Четвертий сезон (рейтинг)"] = "10.02.2024",
    ["П'ятий сезон (фінал)"] = "06.04.2024",
    ["П'ятий сезон (рейтинг)"] = "06.04.2024",
    ["Шостий сезон (фінал)"] = "11.05.2024",
    ["Шостий сезон (рейтинг)"] = "11.05.2024",
    ["Сьомий сезон (фінал)"] = "06.07.2024",
    ["Сьомий сезон (рейтинг)"] = "06.07.2024",
    ["Восьмий сезон (фінал)"] = "10.08.2024",
    ["Восьмий сезон (рейтинг)"] = "10.08.2024",
    ["Дев'ятий сезон (фінал)"] = "28.09.2024",
    ["Дев'ятий сезон (рейтинг)"] = "28.09.2024",
    ["Фінал Року"] = "13.10.2024"
}

local function get_medal_icon(value)
    if not value or value == "" or value == " " then return nil end
    value = mw.text.trim(value)
    
    if value == "" then return nil
    elseif value == "1" then return "[[Файл:Gold.png|20px|link=]]"
    elseif value == "2" then return "[[Файл:Silver.png|20px|link=]]"
    elseif value == "3" then return "[[Файл:Bronze.png|20px|link=]]"
    elseif value == "4" then return "[[Файл:Finalist.png|20px|link=]]"
    elseif value == "S" or mw.ustring.match(value, "^S%d") then return "[[Файл:Star.png|20px|link=]]"
    else return nil
    end
end

local function to_roman(num)
    local romans = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
    return romans[tonumber(num)] or num
end

local function get_tournament_link(header_name)
    local season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(фінал%)")
    if not season_num then
        season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(рейтинг%)")
    end
    
    if season_num then
        local season_map = {
            ["Перший"] = 1, ["Другий"] = 2, ["Третій"] = 3,
            ["Четвертий"] = 4, ["П'ятий"] = 5, ["Шостий"] = 6,
            ["Сьомий"] = 7, ["Восьмий"] = 8, ["Дев'ятий"] = 9
        }
        
        local season_number = season_map[season_num]
        if season_number then
            local roman = to_roman(season_number)
            local type_text = mw.ustring.find(header_name, "фінал") and "Фінал." or "Рейтинг."
            local clean_name = mw.ustring.gsub(header_name, "%s*%(фінал%)%s*", "")
            clean_name = mw.ustring.gsub(clean_name, "%s*%(рейтинг%)%s*", "")
            clean_name = mw.text.trim(clean_name)
            
            return string.format("[[%s|%s сезон. %s]]", clean_name, roman, type_text)
        end
    end
    
    return string.format("[[%s]]", header_name)
end

local function get_player_row(page_title, player_name)
    local table_content = get_table_from_page(page_title)
    if not table_content then return nil end
    
    local first_row = mw.ustring.match(table_content, "{|[^\n]*\n!(.-)[\n]")
    local headers = {}
    
    if first_row then
        for header in mw.ustring.gmatch(first_row, "([^|]+)") do
            local trimmed = mw.text.trim(header)
            if trimmed ~= "" then
                table.insert(headers, trimmed)
            end
        end
    end
    
    if #headers == 0 then return nil end
    
    local cells = find_player_in_table(table_content, player_name)
    if not cells then return nil end
    
    return {headers = headers, cells = cells}
end

-- ================================================
-- PLAYER IMAGE (автовибір аватара)
-- ================================================

function p.player_image(frame)
    local nickname = frame.args.nickname or frame.args[1] or mw.title.getCurrentTitle().text
    local player_file = "Player_" .. nickname .. ".png"
    local placeholder = "MCC_Placeholder.png"
    
    local title = mw.title.new("File:" .. player_file)
    
    local file_to_use
    if title and title.exists then
        file_to_use = player_file
    else
        file_to_use = placeholder
    end
    
    return string.format("[[File:%s|none|alt=Фотографія гравця|center|360px]]", file_to_use)
end

function p.titles(frame)
    local name = frame.args.player
    if not name or name == "" then return "''Відсутні''" end
    
    local titles_data = get_player_row("Титули", name)
    if not titles_data then return "''Відсутні''" end
    
    local prize_data = get_player_row("Призові", name)
    if not prize_data then return error_output("Prize Data Missing", name) end
    
    local results = {}
    
    for i = 2, #titles_data.cells do
        local cell_value = titles_data.cells[i]
        local header = titles_data.headers[i]
        local prize_value = prize_data.cells[i]
        
        if cell_value and cell_value ~= "" and cell_value ~= "-" and header then
            local medal = get_medal_icon(cell_value)
            
            if medal then
                local prize_amount = "0 ₴"
                
                if prize_value and prize_value ~= "" and prize_value ~= "-" then
                    prize_value = mw.ustring.gsub(prize_value, "₴", "")
                    prize_value = mw.text.trim(prize_value)
                    if prize_value ~= "0" then
                        prize_amount = prize_value .. " ₴"
                    end
                end
                
                local date = tournament_dates[header] or "01.01.2023"
                local tournament_link = get_tournament_link(header)
                
                table.insert(results, {
                    date = date,
                    medal = medal,
                    tournament = tournament_link,
                    prize = prize_amount
                })
            end
        end
    end
    
    if #results == 0 then return "''Відсутні''" end
    
    local table_html = {
        '{| class="wikitable sortable" style="font-size: 14.5px;"',
        '|-',
        '! style="" | Дата',
        '! style="text-align: left;" | Місце і турнір',
        '! style="text-align: left;" | Приз'
    }
    
    for _, result in ipairs(results) do
        table.insert(table_html, '|-')
        table.insert(table_html, string.format(
            '| style="width:80px; text-align:center;" | %s || style="text-align:left; padding-left:10px;" | %s %s || style="text-align:right;" | %s',
            result.date, result.medal, result.tournament, result.prize
        ))
    end
    
    table.insert(table_html, '|}')
    return table.concat(table_html, '\n')
end

-- ================================================
-- FETCHDATA4 (цікаві факти)
-- ================================================

local pages_to_search = {
    "Перший сезон", "Другий сезон", "Третій сезон", "Четвертий сезон",
    "П'ятий сезон", "Шостий сезон", "Сьомий сезон", "Восьмий сезон",
    "Дев'ятий сезон", "Mafia Closed Circle", "Ten's Games", "Alternatywa",
    "Майстерня Стратегічного Саморозвитку", "Get Names 01", "Get Names 02",
    "Get Names 03", "Get Names 04", "Get Names 05", "Get Names 06",
    "Get Names 07", "Get Names 08", "Get Names 09", "Women's Closed Cup I",
    "Women's Closed Cup II", "Men's Closed Cup I", "Men's Closed Cup II",
    "Combined Closed Cup I", "Combined Closed Cup II", "Score Open Battle I",
    "Score Open Battle II", "Mafia Closed Cup I", "Mafia Closed Cup I Online",
    "My Closest Circle I", "Фінал Року"
}

local function get_facts_section(page_title)
    local content = get_page_content(page_title)
    if not content then return nil end
    
    local section_start = mw.ustring.find(content, "==%s*[Цц]ікаві%s*[Фф]акти%s*==")
    if not section_start then return nil end
    
    local section_end = mw.ustring.find(content, "\n==[^=]", section_start + 1)
    
    local section_content
    if section_end then
        section_content = mw.ustring.sub(content, section_start, section_end - 1)
    else
        section_content = mw.ustring.sub(content, section_start)
    end
    
    return section_content
end

local function fact_contains_player(fact, player_name)
    local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local pattern1 = "%[%[" .. escaped_name .. "%]%]"
    local pattern2 = "%[%[" .. escaped_name .. "|[^%]]+%]%]"
    
    return mw.ustring.find(fact, pattern1) or mw.ustring.find(fact, pattern2)
end

local function extract_facts(section_content)
    local facts = {}
    for fact in mw.ustring.gmatch(section_content, "%*[^\n]+") do
        table.insert(facts, fact)
    end
    return facts
end

function p.facts(frame)
    local player_name = frame.args.player or frame.args[1] or mw.title.getCurrentTitle().text
    
    local result_parts = {}
    
    for _, page_title in ipairs(pages_to_search) do
        local section = get_facts_section(page_title)
        
        if section then
            local facts = extract_facts(section)
            local player_facts = {}
            
            for _, fact in ipairs(facts) do
                if fact_contains_player(fact, player_name) then
                    table.insert(player_facts, fact)
                end
            end
            
            if #player_facts > 0 then
                local block = string.format("==== %s ====\n", page_title)
                for _, fact in ipairs(player_facts) do
                    block = block .. fact .. "\n"
                end
                table.insert(result_parts, string.format('<div style="margin-bottom: 20px;">\n%s</div>', block))
            end
        end
    end
    
    if #result_parts == 0 then return "" end
    
    return table.concat(result_parts, "\n")
end

-- ================================================
-- FETCHDATA5 (записи ігор) - ВИПРАВЛЕНО
-- ================================================

local function get_tournament_link_games(short_name, full_name)
    if not short_name or short_name == "" then
        return "Невідомо"
    end
    
    if short_name == "Фанова гра" then
        return "Фанова гра"
    end
    
    -- Очищаємо full_name від [[ ]]
    if full_name then
        full_name = clean_wikilinks(full_name)
    end
    
    if full_name and full_name ~= "" and full_name ~= short_name then
        return "[[" .. full_name .. "|" .. short_name .. "]]"
    else
        return "[[" .. short_name .. "]]"
    end
end

local function parse_games_table_rows(table_content)
    local rows = {}
    
    -- Шукаємо рядки після |- (inline формат: все в одному рядку)
    for row in mw.ustring.gmatch(table_content, "|-\n([^\n]+)") do
        -- Пропускаємо заголовки (починаються з !)
        if not mw.ustring.match(row, "^%s*!") then
            table.insert(rows, row)
        end
    end
    
    return rows
end

local function parse_games_row_cells(row)
    local cells = {}
    
    -- Видаляємо початковий |
    row = mw.ustring.gsub(row, "^%s*|%s*", "")
    
    -- Розділяємо по ||
    for cell in mw.ustring.gmatch(row .. "||", "(.-)%s*||%s*") do
        cell = mw.text.trim(cell)
        table.insert(cells, cell)
    end
    
    return cells
end

local function get_player_role(player_name, cells)
    -- Колонки 6-11: мирні
    for i = 6, 11 do
        if cells[i] and clean_wikilinks(cells[i]) == player_name then
            return "Мирний"
        end
    end
    
    -- Колонка 12: шериф
    if cells[12] and clean_wikilinks(cells[12]) == player_name then
        return "Шериф"
    end
    
    -- Колонки 13-14: мафія
    for i = 13, 14 do
        if cells[i] and clean_wikilinks(cells[i]) == player_name then
            return "Мафія"
        end
    end
    
    -- Колонка 15: дон
    if cells[15] and clean_wikilinks(cells[15]) == player_name then
        return "Дон"
    end
    
    return "Невідомо"
end

local function get_player_result(role, game_result)
    if not game_result or game_result == "" then return "Невідомо" end
    
    game_result = mw.text.trim(game_result)
    
    if game_result == "Місто" or game_result == "Мирні" then
        return (role == "Мирний" or role == "Шериф") and "Перемога" or "Поразка"
    end
    
    if game_result == "Мафія" then
        return (role == "Мафія" or role == "Дон") and "Перемога" or "Поразка"
    end
    
    return "Невідомо"
end

local function get_player_games_list(player_name)
    local table_content = get_table_from_page("Ігри")
    if not table_content then return nil end
    
    local rows = parse_games_table_rows(table_content)
    local games = {}
    
    for _, row in ipairs(rows) do
        local cells = parse_games_row_cells(row)
        
        if #cells >= 17 then
            -- Перевіряємо чи гравець у грі (колонки 6-15)
            local player_in_game = false
            for i = 6, 15 do
                if cells[i] and clean_wikilinks(cells[i]) == player_name then
                    player_in_game = true
                    break
                end
            end
            
            if player_in_game then
                local role = get_player_role(player_name, cells)
                local result = get_player_result(role, cells[16])
                
                table.insert(games, {
                    short = cells[3] or "",
                    tournament = cells[4] or "",
                    time = cells[5] or "",
                    result = result,
                    link = cells[17] or ""
                })
            end
        end
    end
    
    return games
end

function p.player_games(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then
        return error_output("No Player Name", "")
    end
    
    local games = get_player_games_list(player_name)
    if not games then
        return error_output("Cannot Load Games", "Ігри")
    end
    
    if #games == 0 then
        return "''Ігор не знайдено''"
    end
    
    local htmlTable = mw.html.create('table')
        :addClass('wikitable sortable')
        :css('width', '100%')
        :css('font-size', '14.5px')
    
    local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Турнір')
    headerRow:tag('th'):wikitext('Час')
    headerRow:tag('th'):wikitext('Результат')
    headerRow:tag('th'):wikitext('Запис')
    
    for _, game in ipairs(games) do
        local row = htmlTable:tag('tr')
        
        -- Турнір (скорочення з посиланням)
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(get_tournament_link_games(game.short, game.tournament))
        
        -- Час
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(game.time)
        
        -- Результат (з кольором)
        local resultCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
        
        if game.result == "Перемога" then
            resultCell:tag('span')
                :css('color', '#4caf50')
                :wikitext('Перемога')
        elseif game.result == "Поразка" then
            resultCell:tag('span')
                :css('color', 'indianred')
                :wikitext('Поразка')
        else
            resultCell:wikitext(game.result)
        end
        
        -- Кнопка перегляду
        local linkCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
        
        if game.link and game.link ~= "" then
            linkCell:wikitext('<span class="game-record-btn">[' .. game.link .. ' ▶︎]</span>')
        end
    end
    
    return tostring(htmlTable)
end

function p.games_count_records(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
    
    local games = get_player_games_list(player_name)
    return games and tostring(#games) or "0"
end

-- ================================================
-- FETCHDATA6 (досягнення в сезонах)
-- ================================================

local function get_season_data(season_number, player_name)
    local season_names = {
        [1] = "Перший сезон", [2] = "Другий сезон", [3] = "Третій сезон",
        [4] = "Четвертий сезон", [5] = "П'ятий сезон", [6] = "Шостий сезон",
        [7] = "Сьомий сезон", [8] = "Восьмий сезон", [9] = "Дев'ятий сезон"
    }
    
    local season_name = season_names[season_number]
    if not season_name then return nil end
    
    local content = get_page_content(season_name)
    if not content then return nil end
    
    local rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.-)\n==")
    if not rating_section then
        rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.*)")
    end
    if not rating_section then return nil end
    
    local table_start = mw.ustring.find(rating_section, "{|")
    local table_end = mw.ustring.find(rating_section, "|}", table_start)
    if not table_start or not table_end then return nil end
    
    local table_content = mw.ustring.sub(rating_section, table_start, table_end + 1)
    
    local current_row = {}
    local all_rows = {}
    
    for line in mw.ustring.gmatch(table_content .. "\n", "([^\n]*)\n") do
        line = mw.text.trim(line)
        
        if line == "|-" then
            if #current_row > 0 then
                table.insert(all_rows, current_row)
            end
            current_row = {}
        elseif mw.ustring.match(line, "^|[^-}]") then
            local cell_content = mw.ustring.gsub(line, "^|%s*", "")
            table.insert(current_row, cell_content)
        end
    end
    
    if #current_row > 0 then
        table.insert(all_rows, current_row)
    end
    
    for _, row in ipairs(all_rows) do
        if #row >= 5 then
            local player_cell = clean_wikilinks(row[2])
            
            if player_cell == player_name then
                return {
                    place = mw.text.trim(row[1]),
                    points = mw.text.trim(row[3]),
                    games = mw.text.trim(row[4]),
                    winrate = mw.text.trim(row[5])
                }
            end
        end
    end
    
    return nil
end

function p.season_achievements(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then
        return "<span style='color:red;'>Ім'я гравця не вказано</span>"
    end
    
    local htmlTable = mw.html.create('table')
        :addClass('wikitable sortable')
        :css('width', '100%')
        :css('font-size', '14.5px')
    
    local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Сезон')
    headerRow:tag('th'):wikitext('Місце')
    headerRow:tag('th'):wikitext('Бали')
    headerRow:tag('th'):wikitext('% перемог')
    headerRow:tag('th'):wikitext('Ігор')
    
    local season_links = {
        [1] = "[[Перший сезон#Рейтинг|01 сезон]]",
        [2] = "[[Другий сезон#Рейтинг|02 сезон]]",
        [3] = "[[Третій сезон#Рейтинг|03 сезон]]",
        [4] = "[[Четвертий сезон#Рейтинг|04 сезон]]",
        [5] = "[[П'ятий сезон#Рейтинг|05 сезон]]",
        [6] = "[[Шостий сезон#Рейтинг|06 сезон]]",
        [7] = "[[Сьомий сезон#Рейтинг|07 сезон]]",
        [8] = "[[Восьмий сезон#Рейтинг|08 сезон]]",
        [9] = "[[Дев'ятий сезон#Рейтинг|09 сезон]]"
    }
    
    for season = 1, 9 do
        local data = get_season_data(season, player_name)
        local row = htmlTable:tag('tr')
        
        row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(season_links[season])
        
        if data then
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.place)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.points)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.winrate)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.games)
        else
            for i = 1, 4 do
                row:tag('td'):css('text-align', 'center'):css('padding', '8px'):css('color', '#666'):wikitext('-')
            end
        end
    end
    
    return tostring(htmlTable)
end

return p