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


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


local page_cache = {}
local page_cache = {}
local table_cache = {}
local table_cache = {}
-- ================================================
-- УТИЛІТИ
-- ================================================
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 error_output(message)
    return '<span style="color:red; font-weight:bold;">Помилка: ' .. message .. '</span>'
end


local function get_page_content(page_name)
local function get_page_content(page_name)
Рядок 30: Рядок 15:
     local title = mw.title.new(page_name)
     local title = mw.title.new(page_name)
     if not title or not title.exists then
     if not title or not title.exists then
        page_cache[page_name] = nil
         return nil
         return nil
     end
     end
Рядок 44: Рядок 30:
      
      
     local content = get_page_content(page_name)
     local content = get_page_content(page_name)
     if not content then return nil end
     if not content then  
        table_cache[page_name] = nil
        return nil  
    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 return nil end
     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)
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     table_cache[page_name] = table_content
     table_cache[page_name] = table_content
     return 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
end


Рядок 70: Рядок 86:
local function parse_row_cells(row)
local function parse_row_cells(row)
     local cells = {}
     local cells = {}
    row = mw.ustring.gsub(row, "^%s*|%s*", "")
      
      
     for cell in mw.ustring.gmatch(row, "|([^|]+)") do
     for line in mw.ustring.gmatch(row, "[^\n]+") do
         local trimmed = mw.text.trim(cell)
         line = mw.text.trim(line)
         if trimmed ~= "" then
       
             table.insert(cells, trimmed)
         if line ~= "|-" and line ~= "" then
             line = mw.ustring.gsub(line, "^|%s*", "")
           
            if line ~= "" then
                table.insert(cells, line)
            end
         end
         end
     end
     end
Рядок 82: Рядок 102:
end
end


local function find_player_data(table_content, player_name)
local function find_player_in_table(table_content, player_name, column_index)
     if not table_content then return nil end
     if not table_content then return nil end
      
      
Рядок 92: Рядок 112:
     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
             return parse_row_cells(row)
             local cells = parse_row_cells(row)
            if column_index and column_index <= #cells then
                return cells[column_index]
            end
            return cells
         end
         end
     end
     end
Рядок 99: Рядок 123:
end
end


-- Конвертація часу (якщо потрібно)
-- ================================================
local function format_time(time_str)
-- FETCHDATA (season_result)
     if not time_str or time_str == "" then return "" end
-- ================================================
 
function p.season_result(frame)
     local season = frame.args.season
    local player = frame.args.player
   
    local season_titles = {
        "Перший сезон", "Другий сезон", "Третій сезон",
        "Четвертий сезон", "П'ятий сезон", "Шостий сезон",
        "Сьомий сезон", "Восьмий сезон", "Дев'ятий сезон"
    }
      
      
     time_str = mw.text.trim(time_str)
     local season_title = season_titles[tonumber(season)]
    if not season_title then return "??" end
      
      
     -- Якщо вже в правильному форматі
     local content = get_page_content(season_title)
     if mw.ustring.find(time_str, "хв") then
     if not content then return "??" end
        return time_str
    end
      
      
    -- Парсимо формат HH:MM:SS або MM:SS
     local rating_section = mw.ustring.match(content, "==%s*Рейтинг%s*==.-{|%s*class%s*=%s*\"wikitable sortable\"(.-)|}")
     local hours, minutes, seconds = mw.ustring.match(time_str, "(%d+):(%d+):(%d+)")
    if not rating_section then return "--" end
      
      
     if not hours then
     local pattern = "|%s*(%d+)%s*|%s*%[%[" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%]%]"
        minutes, seconds = mw.ustring.match(time_str, "(%d+):(%d+)")
    local direct_pattern = "|%s*(%d+)%s*|%s*" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%s*"
        hours = "0"
    end
      
      
     if minutes and seconds then
     local rank = mw.ustring.match(rating_section, pattern) or mw.ustring.match(rating_section, direct_pattern)
        hours = tonumber(hours) or 0
        minutes = tonumber(minutes) or 0
        seconds = tonumber(seconds) or 0
       
        if hours > 0 then
            return string.format("%d год %02d хв %02d с", hours, minutes, seconds)
        else
            return string.format("%d хв %02d с", minutes, seconds)
        end
    end
      
      
     return time_str
     return rank or "--"
end
end


-- ================================================
-- ================================================
-- ФУНКЦІЇ З FETCHDATA
-- FETCHDATA2 (базова статистика)
-- ================================================
-- ================================================


function p.season_result(frame)
function p.games_count(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
     local season_number = frame.args.season or frame.args[2]
     if not player_name or player_name == "" then return "0" end
      
      
     if not player_name or player_name == "" then
    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 2)
        return error_output("Не вказано ім'я гравця")
    return result or "0"
    end
end
 
function p.wins_count(frame)
    local player_name = frame.args.player
     if not player_name or player_name == "" then return "0" end
      
      
     if not season_number or season_number == "" then
    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 3)
        return error_output("Не вказано номер сезону")
    return result or "0"
    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 season_page = nil
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 4)
    local season_names = {
    return result or "0"
        ["1"] = "Перший сезон",
end
        ["2"] = "Другий сезон",
 
        ["3"] = "Третій сезон",
function p.win_rate_colored(frame)
        ["4"] = "Четвертий сезон",
    local player_name = frame.args.player
        ["5"] = "П'ятий сезон",
    if not player_name or player_name == "" then return "0%" end
        ["6"] = "Шостий сезон",
        ["7"] = "Сьомий сезон",
        ["8"] = "Восьмий сезон",
        ["9"] = "Дев'ятий сезон"
    }
      
      
     season_page = season_names[season_number]
     local win_rate = find_player_in_table(get_table_from_page("Статистика"), player_name, 5)
    if not win_rate then return "0%" end
      
      
     if not season_page then
     local rate_num = tonumber(mw.ustring.match(win_rate, "([%d%.]+)"))
        return error_output("Невірний номер сезону")
     if not rate_num then return win_rate end
     end
      
      
     local content = get_page_content(season_page)
     local color = "#4caf50"
     if not content then
     if rate_num < 40 then
         return error_output("Не вдалося завантажити сторінку сезону")
         color = "indianred"
    elseif rate_num < 50 then
        color = "orange"
     end
     end
      
      
     local final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.-)\n==")
     if not mw.ustring.find(win_rate, "%%") then
    if not final_section then
         win_rate = win_rate .. "%"
         final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.*)")
     end
     end
      
      
     if not final_section then
    return string.format('<span style="color:%s;">%s</span>', color, win_rate)
        return error_output("Не знайдено секцію Фінал")
end
    end
 
function p.recruiter(frame)
    local player_name = frame.args.player
     if not player_name or player_name == "" then return "Не вказано" end
      
      
     local table_start = mw.ustring.find(final_section, "{|")
     local table_content = get_table_from_page("Гравці")
     local table_end = mw.ustring.find(final_section, "|}", table_start)
     if not table_content then return "Не вказано" end
      
      
     if not table_start or not table_end then
    local cells = find_player_in_table(table_content, player_name)
        return error_output("Не знайдено таблицю у секції Фінал")
     if not cells or #cells < 2 then return "Не вказано" end
    end
      
      
     local table_content = mw.ustring.sub(final_section, table_start, table_end + 1)
     local raw = mw.text.trim(cells[2])
     local player_data = find_player_data(table_content, player_name)
     if raw == "Відсутній" or raw == "-" or raw == "" then  
   
         return "Не вказано"  
    if not player_data or #player_data < 2 then
         return ""
     end
     end
      
      
    local place = player_data[1]
     return raw
     return place or "—"
end
end


-- ================================================
function p.date_added(frame)
-- ФУНКЦІЇ З FETCHDATA2
     local player_name = frame.args.player
-- ================================================
    if not player_name or player_name == "" then return "Лише Бог знає" end
 
function p.games_count(frame)
     local player_name = frame.args.player or frame.args[1]
      
      
     if not player_name or player_name == "" then
     local raw = find_player_in_table(get_table_from_page("Гравці"), player_name, 3)
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local table_content = get_table_from_page("Статистика")
     if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
    if not table_content then
         return "Лише Бог знає"
         return error_output("Не вдалося завантажити таблицю")
     end
     end
      
      
     local data = find_player_data(table_content, player_name)
     local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")
      
      
     if not data or #data < 3 then
     if day and month and year then
         return "0"
        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
     end
      
      
     return data[3] or "0"
     return "Лише Бог знає"
end
end


function p.wins_count(frame)
function p.foundation(frame)
     local player_name = frame.args.player or frame.args[1]
     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)
      
      
     if not player_name or player_name == "" then
    return (raw or "0") .. " ₴"
        return error_output("Не вказано ім'я гравця")
end
    end
 
function p.prize_pool(frame)
    local player_name = frame.args.player
     if not player_name or player_name == "" then return "0₴" end
      
      
     local table_content = get_table_from_page("Статистика")
     local raw = find_player_in_table(get_table_from_page("Призовий_фонд"), player_name, 2)
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
      
      
     local data = find_player_data(table_content, player_name)
     if not raw or raw == "" or raw == "-" then return "0₴" end
      
      
     if not data or #data < 4 then
     raw = mw.ustring.gsub(raw, "[^%d%s]", "")
        return "0"
     raw = mw.text.trim(raw)
     end
      
      
     return data[4] or "0"
     return (raw or "0") .. " ₴"
end
end


function p.losses_count(frame)
function p.finalist(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
    if not player_name or player_name == "" then return "0/9" end
      
      
     if not player_name or player_name == "" then
     local raw = find_player_in_table(get_table_from_page("Фіналіст"), player_name, 2)
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local table_content = get_table_from_page("Статистика")
     if not raw or raw == "" or raw == "-" then return "0/9" end
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
      
      
     local data = find_player_data(table_content, player_name)
     local number_only = mw.ustring.gsub(raw, "[^%d]", "")
    local count = tonumber(number_only)
      
      
     if not data or #data < 5 then
     if count then
         return "0"
         return string.format("%d/9", count)
     end
     end
      
      
     return data[5] or "0"
     return "0/9"
end
end


function p.win_rate_colored(frame)
function p.foty_rating(frame)
     local player_name = frame.args.player or frame.args[1]
     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 player_name or player_name == "" then
     local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
         return error_output("Не вказано ім'я гравця")
    if not foty_section then
         foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
     end
     end
      
      
     local table_content = get_table_from_page("Статистика")
    if not foty_section then return "—" end
     if not table_content then
   
        return error_output("Не вдалося завантажити таблицю")
     local table_start = mw.ustring.find(foty_section, "{|")
     end
    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)
      
      
     local data = find_player_data(table_content, player_name)
     if not cells or #cells < 3 then return "—" end
      
      
     if not data or #data < 6 then
     local place = strip_html_tags(cells[1]) or "—"
        return '<span style="color:indianred;">0%</span>'
    local rating = strip_html_tags(cells[3]) or ""
    end
      
      
     local winrate = data[6]
     rating = mw.ustring.gsub(rating, "[^%d%-]", "")
    winrate = mw.ustring.gsub(winrate, "%%", "")
     rating = mw.text.trim(rating)
     winrate = mw.text.trim(winrate)
      
      
     local rate = tonumber(winrate) or 0
     if rating and rating ~= "" then
    local color = rate < 40 and "indianred" or "#4caf50"
        return rating .. " (" .. place .. " місце)"
    end
      
      
     return '<span style="color:' .. color .. ';">' .. winrate .. '%</span>'
     return ""
end
end


function p.recruiter(frame)
-- ================================================
     local player_name = frame.args.player or frame.args[1]
-- 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 not player_name or player_name == "" then
     if value == "" then return nil
         return error_output("Не вказано ім'я гравця")
    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
      
      
     local table_content = get_table_from_page("Гравці")
     if season_num then
    if not table_content then
        local season_map = {
        return error_output("Не вдалося завантажити таблицю")
            ["Перший"] = 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
     end
      
      
     local data = find_player_data(table_content, player_name)
    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 not data or #data < 4 then
     if first_row then
         return "Не вказано"
         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
     end
      
      
     local recruiter = mw.text.trim(data[4])
     if #headers == 0 then return nil end
      
      
     if recruiter == "Відсутній" or recruiter == "-" or recruiter == "" then
    local cells = find_player_in_table(table_content, player_name)
        return "Не вказано"
     if not cells then return nil end
    end
      
      
     return recruiter
     return {headers = headers, cells = cells}
end
end


function p.date_added(frame)
-- ================================================
     local player_name = frame.args.player or frame.args[1]
-- 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)
      
      
     if not player_name or player_name == "" then
    local file_to_use
         return error_output("Не вказано ім'я гравця")
     if title and title.exists then
        file_to_use = player_file
    else
         file_to_use = placeholder
     end
     end
      
      
     local table_content = get_table_from_page("Гравці")
     return string.format("[[File:%s|none|alt=Фотографія гравця|center|360px]]", file_to_use)
     if not table_content then
end
        return error_output("Не вдалося завантажити таблицю")
 
    end
function p.titles(frame)
    local name = frame.args.player
     if not name or name == "" then return "''Відсутні''" end
      
      
     local data = find_player_data(table_content, player_name)
     local titles_data = get_player_row("Титули", name)
    if not titles_data then return "''Відсутні''" end
      
      
     if not data or #data < 3 then
    local prize_data = get_player_row("Призові", name)
        return "Лише Бог знає"
     if not prize_data then return error_output("Prize Data Missing", name) end
    end
      
      
     local raw_date = data[3]
     local results = {}
      
      
     if not raw_date or raw_date == "" or raw_date == "-" then
     for i = 2, #titles_data.cells do
        return "Лише Бог знає"
        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
      
      
     local day, month, year = mw.ustring.match(raw_date, "(%d+)%.(%d+)%.(%d+)")
    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;" | Приз'
    }
      
      
     if not day or not month or not year then
     for _, result in ipairs(results) do
         return raw_date
         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
      
      
     local end_date = os.time({year=2024, month=10, day=26})
     table.insert(table_html, '|}')
    local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
     return table.concat(table_html, '\n')
    local days_diff = math.floor((end_date - start_date) / 86400)
   
     return string.format("%s (%d днів)", raw_date, days_diff)
end
end


function p.foundation(frame)
-- ================================================
     local player_name = frame.args.player or frame.args[1]
-- 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
      
      
     if not player_name or player_name == "" then
     local section_end = mw.ustring.find(content, "\n==[^=]", section_start + 1)
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local table_content = get_table_from_page("Фундація")
     local section_content
     if not table_content then
    if section_end then
         return error_output("Не вдалося завантажити таблицю")
        section_content = mw.ustring.sub(content, section_start, section_end - 1)
     else
         section_content = mw.ustring.sub(content, section_start)
     end
     end
      
      
     local data = find_player_data(table_content, player_name)
     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 .. "|[^%]]+%]%]"
      
      
     if not data or #data < 3 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
    local amount = mw.ustring.gsub(data[3], "[^%d]", "")
     return amount
end
end


function p.prize_pool(frame)
function p.facts(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player or frame.args[1] or mw.title.getCurrentTitle().text
      
      
     if not player_name or player_name == "" then
     local result_parts = {}
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local table_content = get_table_from_page("Призовий_фонд")
     for _, page_title in ipairs(pages_to_search) do
    if not table_content then
        local section = get_facts_section(page_title)
        return error_output("Не вдалося завантажити таблицю")
       
        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
      
      
     local data = find_player_data(table_content, player_name)
     if #result_parts == 0 then return "" end
      
      
     if not data or #data < 2 then
     return table.concat(result_parts, "\n")
        return "0"
    end
   
    local amount = mw.ustring.gsub(data[2], "[^%d]", "")
    return amount
end
end


function p.finalist(frame)
-- ================================================
     local player_name = frame.args.player or frame.args[1]
-- FETCHDATA5 (записи ігор) - З РОЛЛЮ ГРАВЦЯ
-- ================================================
 
local function get_tournament_link_games(short_name, full_name)
     if not short_name or short_name == "" then
        return "Невідомо"
    end
      
      
     if not player_name or player_name == "" then
     if short_name == "Фанова гра" then
         return error_output("Не вказано ім'я гравця")
         return "Фанова гра"
     end
     end
      
      
    local table_content = get_table_from_page("Фіналіст")
     if full_name then
     if not table_content then
         full_name = clean_wikilinks(full_name)
         return error_output("Не вдалося завантажити таблицю")
     end
     end
      
      
     local data = find_player_data(table_content, player_name)
     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 = {}
      
      
     if not data or #data < 3 then
     for row in mw.ustring.gmatch(table_content, "|-\n([^\n]+)") do
         return "0"
        if not mw.ustring.match(row, "^%s*!") then
            table.insert(rows, row)
         end
     end
     end
      
      
    local count = mw.ustring.gsub(data[3], "[^%d]", "")
     return rows
     return count
end
end


function p.foty_rating(frame)
local function parse_games_row_cells(row)
     local player_name = frame.args.player or frame.args[1]
     local cells = {}
      
      
     if not player_name or player_name == "" then
     row = mw.ustring.gsub(row, "^%s*|%s*", "")
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local content = get_page_content("Період")
     for cell in mw.ustring.gmatch(row .. "||", "(.-)%s*||%s*") do
    if not content then
        cell = mw.text.trim(cell)
         return "—"
         table.insert(cells, cell)
     end
     end
      
      
     local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
     return cells
    if not foty_section then
end
        foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
 
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
      
      
     if not foty_section then
    -- Колонка 12: шериф
         return ""
     if cells[12] and clean_wikilinks(cells[12]) == player_name then
         return "Шер"
     end
     end
      
      
     local table_start = mw.ustring.find(foty_section, "{|")
     -- Колонки 13-14: мафія
     local table_end = mw.ustring.find(foty_section, "|}", table_start)
     for i = 13, 14 do
   
        if cells[i] and clean_wikilinks(cells[i]) == player_name then
    if not table_start or not table_end then
            return "Маф"
        return "—"
        end
    end
   
    local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
    local data = find_player_data(table_content, player_name)
   
    if not data or #data < 3 then
        return ""
     end
     end
      
      
     local place = mw.text.trim(data[1])
     -- Колонка 15: дон
     local rating = mw.text.trim(data[3])
     if cells[15] and clean_wikilinks(cells[15]) == player_name then
   
         return "Дон"
    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 rating .. " (" .. place .. " місце)"
     end
     end
      
      
Рядок 475: Рядок 696:
end
end


-- ================================================
local function get_player_result(role, game_result)
-- ФУНКЦІЇ З FETCHDATA5 (player_games)
     if not game_result or game_result == "" then return "Невідомо" end
-- ================================================
 
function p.player_games(frame)
     local player_name = frame.args.player or frame.args[1]
      
      
     if not player_name or player_name == "" then
     game_result = mw.text.trim(game_result)
        return error_output("Не вказано ім'я гравця")
    end
      
      
     local page_content = get_page_content("Записи_ігор")
     if game_result == "Місто" or game_result == "Мирні" then
    if not page_content then
         return (role == "Мир" or role == "Шер") and "Перемога" or "Поразка"
         return error_output("Не вдалося завантажити сторінку Записи_ігор")
     end
     end
      
      
     local table_start = mw.ustring.find(page_content, "{|")
     if game_result == "Мафія" then
    local table_end = mw.ustring.find(page_content, "|}", table_start)
         return (role == "Маф" or role == "Дон") and "Перемога" or "Поразка"
   
    if not table_start or not table_end then
         return error_output("Не знайдено таблицю")
     end
     end
      
      
     local table_content = mw.ustring.sub(page_content, table_start, table_end + 1)
     return "Невідомо"
     local rows = parse_table_rows(table_content)
end
 
local function get_player_games_list(player_name)
     local table_content = get_table_from_page("Ігри")
    if not table_content then return nil end
      
      
     if #rows < 1 then
     local rows = parse_games_table_rows(table_content)
        return error_output("Таблиця порожня")
     local games = {}
    end
   
    local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local player_pattern = "%[%[" .. escaped_name .. "%]%]"
   
     local player_games = {}
      
      
     for _, row in ipairs(rows) do
     for _, row in ipairs(rows) do
         if mw.ustring.find(row, player_pattern) then
         local cells = parse_games_row_cells(row)
             local cells = parse_row_cells(row)
       
        if #cells >= 17 then
             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 #cells >= 17 then
          if player_in_game then
                 local game_type = cells[2]
                 local role = get_player_role(player_name, cells)
                local tournament = cells[4]
                 local result = get_player_result(role, cells[16])
                local time = cells[5]
                 local result = cells[16]
                local link = cells[17]
                  
                  
                 local position = nil
                 -- Конвертуємо час з "37 хв 26 с" в "37:26"
                 local role = nil
                 local time_raw = cells[5] or ""
               
                local minutes, seconds = mw.ustring.match(time_raw, "(%d+)%s*хв%s*(%d+)%s*с")
                for j = 6, 15 do
                local time_formatted = time_raw
                    local cell_player = clean_wikilinks(cells[j])
                if minutes and seconds then
                    if cell_player == player_name then
                    time_formatted = minutes .. ":" .. string.format("%02d", tonumber(seconds))
                        position = j - 5
                       
                        if j >= 6 and j <= 11 then
                            role = "Мирний"
                        elseif j == 12 then
                            role = "Шериф"
                        elseif j >= 13 and j <= 14 then
                            role = "Мафія"
                        elseif j == 15 then
                            role = "Дон"
                        end
                        break
                    end
                 end
                 end
                  
                  
                 if position and role then
                 table.insert(games, {
                    table.insert(player_games, {
                    short = cells[3] or "",
                        type = game_type,
                    tournament = cells[4] or "",
                        tournament = tournament,
                    time = time_formatted,
                        time = time,
                    role = role,
                        position = position,
                    result = result,
                        role = role,
                    link = cells[17] or ""
                        result = result,
                })
                        link = link
                    })
                end
             end
             end
         end
         end
     end
     end
      
      
     if #player_games == 0 then
    return games
         return "''Записів ігор не знайдено''"
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
     end
      
      
     local output = {}
     local games = get_player_games_list(player_name)
     table.insert(output, '{| class="wikitable sortable" style="font-size: 14px;"')
     if not games then
    table.insert(output, '|-')
        return error_output("Cannot Load Games", "Ігри")
    table.insert(output, '! №')
     end
    table.insert(output, '! Тип')
    table.insert(output, '! Турнір')
     table.insert(output, '! Час')
    table.insert(output, '! Позиція')
    table.insert(output, '! Роль')
    table.insert(output, '! Результат')
    table.insert(output, '! Відео')
      
      
     for i, game in ipairs(player_games) do
    if #games == 0 then
         table.insert(output, '|-')
        return "''Ігор не знайдено''"
        table.insert(output, '| style="text-align:center;" | ' .. i)
    end
        table.insert(output, '| ' .. game.type)
   
         table.insert(output, '| ' .. game.tournament)
    local htmlTable = mw.html.create('table')
        table.insert(output, '| style="text-align:center;" | ' .. game.time)
        :addClass('wikitable sortable')
        table.insert(output, '| style="text-align:center;" | ' .. game.position)
        :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 role_color = "#fff"
   
     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))
       
      -- Роль (білий колір)
         local roleCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
          
          
         local role_color = ""
         roleCell:tag('span')
        if game.role == "Мирний" then
            :css('color', '#fff')
             role_color = "#90EE90"
            :wikitext(game.role)
        elseif game.role == "Шериф" then
       
             role_color = "#4169E1"
        -- Час
         elseif game.role == "Мафія" then
        row:tag('td')
             role_color = "#DC143C"
            :css('text-align', 'center')
        elseif game.role == "Дон" then
             :css('padding', '8px')
             role_color = "#8B0000"
            :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
         end
          
          
         table.insert(output, '| style="background-color:' .. role_color .. '; text-align:center;" | ' .. game.role)
         -- Кнопка перегляду
        local linkCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
          
          
        local result_style = ""
         if game.link and game.link ~= "" then
         if game.result == "Місто" or game.result == "Мирні" then
             linkCell:wikitext('<span class="game-record-btn">[' .. game.link .. ' ▶︎]</span>')
             result_style = 'style="color:#4caf50; font-weight:bold;"'
        elseif game.result == "Мафія" then
            result_style = 'style="color:#f44336; font-weight:bold;"'
         end
         end
       
        table.insert(output, '| ' .. result_style .. ' | ' .. game.result)
        table.insert(output, '| [' .. game.link .. ' YouTube]')
     end
     end
      
      
    table.insert(output, '|}')
     return tostring(htmlTable)
   
     return table.concat(output, '\n')
end
end


function p.games_count_records(frame)
function p.games_count_records(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
      
      
     if not player_name or player_name == "" then
     local games = get_player_games_list(player_name)
         return "0"
    return games and tostring(#games) or "0"
     end
end
 
-- ================================================
-- FETCHDATA6 (досягнення в сезонах)
-- ================================================
 
local function get_season_data(season_number, player_name)
    local season_names = {
        [1] = "Перший сезон", [2] = "Другий сезон", [3] = "Третій сезон",
         [4] = "Четвертий сезон", [5] = "П'ятий сезон", [6] = "Шостий сезон",
        [7] = "Сьомий сезон", [8] = "Восьмий сезон", [9] = "Дев'ятий сезон"
     }
      
      
     local page_content = get_page_content("Записи_ігор")
     local season_name = season_names[season_number]
     if not page_content then
     if not season_name then return nil end
        return "0"
    end
      
      
     local table_start = mw.ustring.find(page_content, "{|")
     local content = get_page_content(season_name)
     local table_end = mw.ustring.find(page_content, "|}", table_start)
     if not content then return nil end
      
      
     if not table_start or not table_end then
    local rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.-)\n==")
         return "0"
     if not rating_section then
         rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.*)")
     end
     end
    if not rating_section then return nil end
      
      
     local table_content = mw.ustring.sub(page_content, table_start, table_end + 1)
    local table_start = mw.ustring.find(rating_section, "{|")
    local rows = parse_table_rows(table_content)
    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 escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local current_row = {}
     local player_pattern = "%[%[" .. escaped_name .. "%]%]"
     local all_rows = {}
      
      
    local count = 0
     for line in mw.ustring.gmatch(table_content .. "\n", "([^\n]*)\n") do
     for _, row in ipairs(rows) do
        line = mw.text.trim(line)
         if mw.ustring.find(row, player_pattern) then
       
             count = count + 1
         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
     end
     end
      
      
    return tostring(count)
     if #current_row > 0 then
end
         table.insert(all_rows, current_row)
 
-- ================================================
-- ФУНКЦІЇ З FETCHDATA6 (season_achievements)
-- ================================================
 
function p.season_achievements(frame)
    local player_name = frame.args.player or frame.args[1]
   
     if not player_name or player_name == "" then
         return error_output("Не вказано ім'я гравця")
     end
     end
      
      
    local season_data = {}
     for _, row in ipairs(all_rows) do
    local season_names = {
         if #row >= 5 then
        "Перший сезон",
             local player_cell = clean_wikilinks(row[2])
        "Другий сезон",
        "Третій сезон",
        "Четвертий сезон",
        "П'ятий сезон",
        "Шостий сезон",
        "Сьомий сезон",
        "Восьмий сезон",
        "Дев'ятий сезон"
    }
   
     for i, season_name in ipairs(season_names) do
         local content = get_page_content(season_name)
       
        if content then
             local final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.-)\n==")
            if not final_section then
                final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.*)")
            end
              
              
             if final_section then
             if player_cell == player_name then
                 local table_start = mw.ustring.find(final_section, "{|")
                 return {
                local table_end = mw.ustring.find(final_section, "|}", table_start)
                    place = mw.text.trim(row[1]),
               
                    points = mw.text.trim(row[3]),
                if table_start and table_end then
                     games = mw.text.trim(row[4]),
                     local table_content = mw.ustring.sub(final_section, table_start, table_end + 1)
                     winrate = mw.text.trim(row[5])
                    local player_data = find_player_data(table_content, player_name)
                 }
                      
                    if player_data and #player_data >= 1 then
                        table.insert(season_data, {
                            season = i,
                            place = player_data[1] or "—"
                        })
                    end
                 end
             end
             end
         end
         end
     end
     end
      
      
     if #season_data == 0 then
    return nil
         return "''Гравець не брав участі в жодному сезоні''"
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
     end
      
      
     local output = {}
     local htmlTable = mw.html.create('table')
    table.insert(output, '{| class="wikitable" style="text-align:center; font-size: 14px;"')
        :addClass('wikitable sortable')
    table.insert(output, '|-')
        :css('width', '100%')
        :css('font-size', '14.5px')
      
      
     for _, data in ipairs(season_data) do
     local headerRow = htmlTable:tag('tr')
        table.insert(output, '! [[' .. season_names[data.season] .. '|' .. data.season .. ']]')
    headerRow:tag('th'):wikitext('Сезон')
     end
    headerRow:tag('th'):wikitext('Місце')
     headerRow:tag('th'):wikitext('Бали')
    headerRow:tag('th'):wikitext('% перемог')
    headerRow:tag('th'):wikitext('Ігор')
      
      
     table.insert(output, '|-')
     local season_links = {
        [1] = "[[Перший сезон#Рейтинг|01 сезон]]",
        [2] = "[[Другий сезон#Рейтинг|02 сезон]]",
        [3] = "[[Третій сезон#Рейтинг|03 сезон]]",
        [4] = "[[Четвертий сезон#Рейтинг|04 сезон]]",
        [5] = "[[П'ятий сезон#Рейтинг|05 сезон]]",
        [6] = "[[Шостий сезон#Рейтинг|06 сезон]]",
        [7] = "[[Сьомий сезон#Рейтинг|07 сезон]]",
        [8] = "[[Восьмий сезон#Рейтинг|08 сезон]]",
        [9] = "[[Дев'ятий сезон#Рейтинг|09 сезон]]"
    }
      
      
     for _, data in ipairs(season_data) do
     for season = 1, 9 do
         table.insert(output, '| ' .. data.place)
        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
      
      
    table.insert(output, '|}')
     return tostring(htmlTable)
   
     return table.concat(output, '\n')
end
end


return p
return p

Поточна версія на 01:42, 29 грудня 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
    
    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 = {}
    
    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
            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])
                
                -- Конвертуємо час з "37 хв 26 с" в "37:26"
                local time_raw = cells[5] or ""
                local minutes, seconds = mw.ustring.match(time_raw, "(%d+)%s*хв%s*(%d+)%s*с")
                local time_formatted = time_raw
                if minutes and seconds then
                    time_formatted = minutes .. ":" .. string.format("%02d", tonumber(seconds))
                end
                
                table.insert(games, {
                    short = cells[3] or "",
                    tournament = cells[4] or "",
                    time = time_formatted,
                    role = role,
                    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('🏆')
    headerRow:tag('th'):wikitext('Запис')

    -- Колір для ролей (білий)
    local role_color = "#fff"
    
    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))
        
       -- Роль (білий колір)
        local roleCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
        
        roleCell:tag('span')
            :css('color', '#fff')
            :wikitext(game.role)
        
        -- Час
        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