- 13
- 9
- Версия MoonLoader
- .026-beta
Приветствую, мой друг.
Я работаю над Lua-скриптом, который отслеживает сообщения из чата в GTA SA:MP. Сценарий такой: есть определённый игрок (допустим, X) и список ключевых слов (например, «член»). Когда игрок X пишет сообщение, содержащее это слово — скрипт срабатывает и выводит его текст мне в чат.
Проблема: всё отлично работает с английскими словами, но русские скрипт вообще не распознаёт. Кодировка используется Windows-1251.
Прошу помощи:
Я работаю над Lua-скриптом, который отслеживает сообщения из чата в GTA SA:MP. Сценарий такой: есть определённый игрок (допустим, X) и список ключевых слов (например, «член»). Когда игрок X пишет сообщение, содержащее это слово — скрипт срабатывает и выводит его текст мне в чат.
Проблема: всё отлично работает с английскими словами, но русские скрипт вообще не распознаёт. Кодировка используется Windows-1251.
Прошу помощи:
- Как сделать так, чтобы обработка текста работала как с английскими, так и с русскими словами?
- По желанию: было бы круто добавить интеграцию с Telegram-ботом, чтобы уведомления приходили и туда. Если в этом разбираешься — подскажи.
lua:
-- Сохранять в кодировке Windows-1251
local imgui = require 'mimgui'
local ffi = require 'ffi'
local vkeys = require 'vkeys'
local sampev = require 'lib.samp.events'
local inicfg = require 'inicfg'
local wm = require 'windows.message'
local new, str, sizeof = imgui.new, ffi.string, ffi.sizeof
-- Переменные интерфейса
local renderWindow = new.bool()
local darkTheme = new.bool()
local playerName = new.char[256]()
local keyWord = new.char[256]()
local playerID = new.char[16]()
local telegramToken = new.char[256]()
local telegramChatID = new.char[256]()
local searchQuery = new.char[256]()
local sizeX, sizeY = getScreenResolution()
-- Переменные вкладок
local tabs = {
{name = "Настройки", id = 1},
{name = "Логи", id = 2},
{name = "Статистика", id = 3},
{name = "Telegram", id = 4}
}
-- Переменные логов и статистики
local logs = {}
local playerStats = {}
local maxLogsDisplay = 100
local maxLogsStored = 1000
local logDaysToLoad = 7
-- Загрузка конфигурации из файла
local config = inicfg.load({
settings = {
players = "sigmaboy",
keywords = "hi,ку,привет,член",
darkTheme = true,
telegramToken = "7717049161:AAFnmwzAyb11Y2jhiZFl-7Ij10wLfbtsoUA",
telegramChatID = "838131789",
windowPosX = sizeX / 2,
windowPosY = sizeY / 2,
windowWidth = 600,
windowHeight = 400,
monitorChat = true,
monitorServer = true
}
}, "monitor.ini")
-- Создание директории для логов
local function createLogsDir()
local path = "moonloader/logs/monitor"
local file = io.open(path .. "/test.tmp", "w")
if file then
file:close()
os.remove(path .. "/test.tmp")
return path
else
os.execute("mkdir moonloader\\logs\\monitor")
return path
end
end
-- Очистка текста от SAMP-цветов
function cleanSampText(text)
if not text then return "" end
return text:gsub("{%x%x%x%x%x%x}", ""):gsub("{%x%x%x%x%x%x%x%x}", "")
end
-- Безопасная обработка строк
local function safeString(str)
return type(str) == "string" and str or ""
end
-- Конвертация из UTF-8 в Windows-1251 (для Telegram)
local function utf8_to_cp1251(utf8_str)
local cp1251_table = {
[0x410] = 0xC0, [0x411] = 0xC1, [0x412] = 0xC2, [0x413] = 0xC3, [0x414] = 0xC4, [0x415] = 0xC5,
[0x416] = 0xC6, [0x417] = 0xC7, [0x418] = 0xC8, [0x419] = 0xC9, [0x41A] = 0xCA, [0x41B] = 0xCB,
[0x41C] = 0xCC, [0x41D] = 0xCD, [0x41E] = 0xCE, [0x41F] = 0xCF, [0x420] = 0xD0, [0x421] = 0xD1,
[0x422] = 0xD2, [0x423] = 0xD3, [0x424] = 0xD4, [0x425] = 0xD5, [0x426] = 0xD6, [0x427] = 0xD7,
[0x428] = 0xD8, [0x429] = 0xD9, [0x42A] = 0xDA, [0x42B] = 0xDB, [0x42C] = 0xDC, [0x42D] = 0xDD,
[0x42E] = 0xDE, [0x42F] = 0xDF, [0x430] = 0xE0, [0x431] = 0xE1, [0x432] = 0xE2, [0x433] = 0xE3,
[0x434] = 0xE4, [0x435] = 0xE5, [0x436] = 0xE6, [0x437] = 0xE7, [0x438] = 0xE8, [0x439] = 0xE9,
[0x43A] = 0xEA, [0x43B] = 0xEB, [0x43C] = 0xEC, [0x43D] = 0xED, [0x43E] = 0xEE, [0x43F] = 0xEF,
[0x440] = 0xF0, [0x441] = 0xF1, [0x442] = 0xF2, [0x443] = 0xF3, [0x444] = 0xF4, [0x445] = 0xF5,
[0x446] = 0xF6, [0x447] = 0xF7, [0x448] = 0xF8, [0x449] = 0xF9, [0x44A] = 0xFA, [0x44B] = 0xFB,
[0x44C] = 0xFC, [0x44D] = 0xFD, [0x44E] = 0xFE, [0x44F] = 0xFF, [0x401] = 0xA8, [0x451] = 0xB8
}
local result = {}
local i = 1
while i <= #utf8_str do
local byte = utf8_str:byte(i)
if byte < 128 then
table.insert(result, string.char(byte))
i = i + 1
elseif byte >= 0xC0 and byte <= 0xDF then
local byte2 = utf8_str:byte(i + 1) or 0
local unicode = ((byte - 0xC0) * 64) + (byte2 - 0x80)
local cp1251_char = cp1251_table[unicode]
if cp1251_char then
table.insert(result, string.char(cp1251_char))
else
table.insert(result, "?")
end
i = i + 2
else
table.insert(result, "?")
i = i + 1
end
end
return table.concat(result)
end
-- Загрузка логов из файлов
local function loadLogs()
logs = {}
local path = createLogsDir()
for i = 0, logDaysToLoad - 1 do
local date = os.date("%Y-%m-%d", os.time() - i * 86400)
local filename = path .. "/" .. date .. ".log"
local file = io.open(filename, "r")
if file then
for line in file:lines() do
local timestamp, player, keyword, messageType, message = line:match("%[(.-)%] (.-) %-> (.-) %((.-)%): (.*)")
if timestamp and player and keyword and messageType and message then
local logEntry = {
timestamp = os.time({year = date:sub(1, 4), month = date:sub(6, 7), day = date:sub(9, 10)}),
player = player,
keyword = keyword,
messageType = messageType,
message = message,
date = timestamp
}
table.insert(logs, 1, logEntry)
if not playerStats[player] then
playerStats[player] = {count = 0, lastSeen = ""}
end
playerStats[player].count = playerStats[player].count + 1
playerStats[player].lastSeen = timestamp
end
end
file:close()
end
end
while #logs > maxLogsStored do
table.remove(logs, #logs)
end
end
-- Сохранение лога
local function saveLog(player, keyword, message, messageType, timestamp)
local cleanMessage = cleanSampText(message)
local logEntry = {
timestamp = timestamp or os.time(),
player = player,
keyword = keyword,
messageType = messageType or "server",
message = cleanMessage,
date = os.date("%d.%m.%Y %H:%M:%S", timestamp or os.time())
}
table.insert(logs, 1, logEntry)
if #logs > maxLogsStored then table.remove(logs, #logs) end
local path = createLogsDir()
local filename = path .. "/" .. os.date("%Y-%m-%d") .. ".log"
local file = io.open(filename, "a")
if file then
file:write(string.format("[%s] %s -> %s (%s): %s\n", logEntry.date, player, keyword, messageType, cleanMessage))
file:close()
else
sampAddChatMessage("[МОНИТОРИНГ] Ошибка записи лога!", -1)
end
if not playerStats[player] then
playerStats[player] = {count = 0, lastSeen = ""}
end
playerStats[player].count = playerStats[player].count + 1
playerStats[player].lastSeen = logEntry.date
end
-- Отправка уведомления в Telegram
local function sendToTelegram(player, keyword, message, messageType)
local token = safeString(config.settings.telegramToken)
local chatID = safeString(config.settings.telegramChatID)
if token == "" or chatID == "" then
sampAddChatMessage("[TELEGRAM] Токен или Chat ID не указаны!", -1)
return
end
lua_thread.create(function()
local cleanMessage = cleanSampText(message or "")
print(string.format("[DEBUG] Отправка в Telegram: player=%s, keyword=%s, type=%s", player or "nil", keyword or "nil", messageType or "server"))
local utfPlayer = safeString(player or "Unknown")
local utfKeyword = safeString(keyword or "Unknown")
local utfMessage = safeString(cleanMessage or "No message")
local timeStr = os.date("%d.%m.%Y %H:%M:%S")
local typeStr = messageType == "chat" and "ЧАТ" or "СЕРВЕР"
local text = string.format("?? МОНИТОРИНГ (%s)\n\n?? Игрок: %s\n?? Слово: %s\n?? Сообщение: %s\n? Время: %s",
typeStr, utfPlayer, utfKeyword, utfMessage, timeStr)
-- URL encode для Telegram
text = text:gsub("([^%w _%%%-%.~])", function(c)
return string.format("%%%02X", string.byte(c))
end):gsub(" ", "%%20")
local url = string.format("https://api.telegram.org/bot%s/sendMessage", token)
local postData = string.format("chat_id=%s&text=%s&parse_mode=HTML", chatID, text)
local wininet = ffi.load('wininet')
ffi.cdef[[
void* InternetOpenA(const char* lpszAgent, unsigned long dwAccessType, const char* lpszProxy,
const char* lpszProxyBypass, unsigned long dwFlags);
void* InternetConnectA(void* hInternet, const char* lpszServerName, unsigned short nServerPort,
const char* lpszUserName, const char* lpszPassword, unsigned long dwService,
unsigned long dwFlags, unsigned long dwContext);
void* HttpOpenRequestA(void* hConnect, const char* lpszVerb, const char* lpszObjectName,
const char* lpszVersion, const char* lpszReferrer,
const char** lplpszAcceptTypes, unsigned long dwFlags, unsigned long dwContext);
int HttpSendRequestA(void* hRequest, const char* lpszHeaders, unsigned long dwHeadersLength,
const void* lpOptional, unsigned long dwOptionalLength);
int InternetReadFile(void* hFile, char* lpBuffer, unsigned long dwNumberOfBytesToRead, unsigned long* lpdwNumberOfBytesRead);
int InternetCloseHandle(void* hInternet);
]]
local hInternet = wininet.InternetOpenA("TelegramBot/1.0", 1, nil, nil, 0)
if hInternet == nil then
sampAddChatMessage("[TELEGRAM] Ошибка инициализации Internet!", -1)
return
end
local hConnect = wininet.InternetConnectA(hInternet, "api.telegram.org", 443, nil, nil, 3, 0, 0)
if hConnect == nil then
sampAddChatMessage("[TELEGRAM] Ошибка подключения к серверу!", -1)
wininet.InternetCloseHandle(hInternet)
return
end
local requestPath = string.format("/bot%s/sendMessage", token)
local hRequest = wininet.HttpOpenRequestA(hConnect, "POST", requestPath, nil, nil, nil, 0x00800000, 0)
if hRequest == nil then
sampAddChatMessage("[TELEGRAM] Ошибка создания запроса!", -1)
wininet.InternetCloseHandle(hConnect)
wininet.InternetCloseHandle(hInternet)
return
end
local headers = "Content-Type: application/x-www-form-urlencoded\r\n"
local postDataBuffer = ffi.new("char[?]", #postData + 1, postData)
local success = wininet.HttpSendRequestA(hRequest, headers, #headers, postDataBuffer, #postData)
if success == 0 then
sampAddChatMessage("[TELEGRAM] Ошибка отправки запроса!", -1)
else
local buffer = ffi.new("char[4096]")
local bytesRead = ffi.new("unsigned long[1]")
local response = ""
while wininet.InternetReadFile(hRequest, buffer, 4096, bytesRead) ~= 0 and bytesRead[0] > 0 do
response = response .. ffi.string(buffer, bytesRead[0])
end
if response:find('"ok":true') then
sampAddChatMessage("[TELEGRAM] ? Уведомление отправлено!", -1)
else
sampAddChatMessage("[TELEGRAM] ? Ошибка отправки", -1)
print("[TELEGRAM DEBUG] Response: " .. response)
end
end
wininet.InternetCloseHandle(hRequest)
wininet.InternetCloseHandle(hConnect)
wininet.InternetCloseHandle(hInternet)
end)
end
-- Инициализация ImGui
imgui.OnInitialize(function()
imgui.GetIO().IniFilename = nil
local safePlayers = safeString(config.settings.players)
local safeKeywords = safeString(config.settings.keywords)
local safeToken = safeString(config.settings.telegramToken)
local safeChatID = safeString(config.settings.telegramChatID)
imgui.StrCopy(playerName, safePlayers)
imgui.StrCopy(keyWord, safeKeywords)
imgui.StrCopy(telegramToken, safeToken)
imgui.StrCopy(telegramChatID, safeChatID)
darkTheme[0] = config.settings.darkTheme
if darkTheme[0] then
imgui.StyleColorsDark()
else
imgui.StyleColorsLight()
end
print(string.format("[DEBUG] Загруженные настройки: players=%s, keywords=%s", safePlayers, safeKeywords))
loadLogs()
end)
-- Функция поиска текста (улучшенная для кириллицы)
local function findText(haystack, needle)
if not haystack or not needle or needle == "" then
return false
end
local cleanHaystack = cleanSampText(haystack):lower()
local cleanNeedle = cleanSampText(needle):lower()
-- Удаляем лишние пробелы
cleanHaystack = cleanHaystack:gsub("%s+", " "):gsub("^%s*(.-)%s*$", "%1")
cleanNeedle = cleanNeedle:gsub("%s+", " "):gsub("^%s*(.-)%s*$", "%1")
print(string.format("[DEBUG] Поиск: '%s' в '%s'", cleanNeedle, cleanHaystack))
return cleanHaystack:find(cleanNeedle, 1, true) ~= nil
end
-- Удаление лишних пробелов
local function trim(s)
return s:gsub("^%s*(.-)%s*$", "%1")
end
-- Сохранение конфигурации
local function saveConfig()
config.settings.players = str(playerName)
config.settings.keywords = str(keyWord)
config.settings.telegramToken = str(telegramToken)
config.settings.telegramChatID = str(telegramChatID)
inicfg.save(config, "monitor.ini")
end
-- Добавление игрока по ID
local function addPlayerByID()
local id = tonumber(str(playerID))
if id and sampIsPlayerConnected(id) then
local nickname = sampGetPlayerNickname(id)
if nickname then
local currentPlayers = str(playerName)
if currentPlayers == "" then
currentPlayers = nickname
else
currentPlayers = currentPlayers .. ", " .. nickname
end
imgui.StrCopy(playerName, currentPlayers)
config.settings.players = currentPlayers
saveConfig()
sampAddChatMessage("[МОНИТОРИНГ] Игрок " .. nickname .. " (ID: " .. id .. ") добавлен!", -1)
imgui.StrCopy(playerID, "")
end
else
sampAddChatMessage("[МОНИТОРИНГ] Игрок с ID " .. str(playerID) .. " не найден!", -1)
end
end
-- Проверка сообщения на совпадения
local function checkMessage(text, messageType)
local targetPlayers = safeString(config.settings.players)
local targetWords = safeString(config.settings.keywords)
if targetPlayers == "" or targetWords == "" then
return false
end
local cleanText = cleanSampText(text)
print(string.format("[DEBUG] Проверка %s сообщения: %s", messageType, cleanText))
for player in targetPlayers:gmatch("[^,]+") do
player = trim(player)
if player ~= "" and findText(cleanText, player) then
for word in targetWords:gmatch("[^,]+") do
word = trim(word)
if word ~= "" and findText(cleanText, word) then
print(string.format("[DEBUG] Совпадение найдено: игрок='%s', слово='%s'", player, word))
saveLog(player, word, text, messageType)
sendToTelegram(player, word, text, messageType)
local typeStr = messageType == "chat" and "ЧАТ" or "СЕРВЕР"
local message = string.format("[МОНИТОРИНГ-%s] Игрок {00FF00}%s {FFFFFF}написал: {FFFF00}%s", typeStr, player, word)
sampAddChatMessage(message, -1)
sampAddChatMessage("[ТЕКСТ] {FFFFFF}" .. cleanText, -1)
return true
end
end
end
end
return false
end
-- Основное окно ImGui
imgui.OnFrame(
function() return renderWindow[0] end,
function()
if darkTheme[0] then
imgui.StyleColorsDark()
else
imgui.StyleColorsLight()
end
imgui.SetNextWindowPos(imgui.ImVec2(config.settings.windowPosX, config.settings.windowPosY), imgui.Cond.FirstUseEver)
imgui.SetNextWindowSize(imgui.ImVec2(config.settings.windowWidth, config.settings.windowHeight), imgui.Cond.FirstUseEver)
if imgui.Begin("Мониторинг игроков v2.1", renderWindow) then
if imgui.BeginTabBar("MainTabs") then
if imgui.BeginTabItem("Настройки") then
imgui.Text("Основные настройки:")
imgui.Separator()
if imgui.InputTextMultiline("Игроки (через запятую)", playerName, sizeof(playerName), imgui.ImVec2(550, 80)) then
config.settings.players = str(playerName)
saveConfig()
end
imgui.Text("Добавить игрока по ID:")
imgui.SameLine()
imgui.PushItemWidth(100)
imgui.InputText("##playerID", playerID, sizeof(playerID))
imgui.PopItemWidth()
imgui.SameLine()
if imgui.Button("Добавить") then
addPlayerByID()
end
imgui.Spacing()
if imgui.InputTextMultiline("Ключевые слова (через запятую)", keyWord, sizeof(keyWord), imgui.ImVec2(550, 80)) then
config.settings.keywords = str(keyWord)
saveConfig()
end
imgui.Spacing()
imgui.Separator()
if imgui.Checkbox("Темная тема", darkTheme) then
config.settings.darkTheme = darkTheme[0]
saveConfig()
end
imgui.Text("Мониторить:")
if imgui.Checkbox("Чат игроков", new.bool(config.settings.monitorChat or true)) then
config.settings.monitorChat = not config.settings.monitorChat
saveConfig()
end
imgui.SameLine()
if imgui.Checkbox("Серверные сообщения", new.bool(config.settings.monitorServer or true)) then
config.settings.monitorServer = not config.settings.monitorServer
saveConfig()
end
imgui.EndTabItem()
end
if imgui.BeginTabItem("Логи") then
imgui.Text("Поиск в логах:")
imgui.SameLine()
imgui.InputText("##search", searchQuery, sizeof(searchQuery))
imgui.SameLine()
if imgui.Button("Экспорт") then
local path = createLogsDir()
local filename = path .. "/export_" .. os.date("%Y%m%d_%H%M%S") .. ".txt"
local file = io.open(filename, "w")
if file then
for _, log in ipairs(logs) do
file:write(string.format("[%s] %s -> %s (%s): %s\n", log.date, log.player, log.keyword, log.messageType or "server", log.message))
end
file:close()
sampAddChatMessage("[ЭКСПОРТ] Логи сохранены: " .. filename, -1)
else
sampAddChatMessage("[ЭКСПОРТ] Ошибка сохранения логов!", -1)
end
end
imgui.Separator()
if imgui.BeginChild("LogsChild", imgui.ImVec2(0, 250), true) then
local searchText = str(searchQuery)
for i, log in ipairs(logs) do
if i > maxLogsDisplay then break end
local shouldShow = searchText == "" or
findText(log.player, searchText) or
findText(log.keyword, searchText) or
findText(log.message, searchText)
if shouldShow then
local typeColor = log.messageType == "chat" and imgui.ImVec4(0.3, 0.8, 1.0, 1.0) or imgui.ImVec4(0.8, 0.8, 0.8, 1.0)
local typeText = log.messageType == "chat" and "[ЧАТ]" or "[СЕРВЕР]"
imgui.TextColored(typeColor, typeText)
imgui.SameLine()
imgui.TextColored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), log.date)
imgui.SameLine()
imgui.TextColored(imgui.ImVec4(0.3, 1.0, 0.3, 1.0), log.player)
imgui.SameLine()
imgui.Text("->")
imgui.SameLine()
imgui.TextColored(imgui.ImVec4(1.0, 1.0, 0.3, 1.0), log.keyword)
imgui.TextWrapped(log.message)
imgui.Separator()
end
end
imgui.EndChild()
end
imgui.EndTabItem()
end
if imgui.BeginTabItem("Статистика") then
imgui.Text("Статистика игроков:")
imgui.Separator()
if imgui.BeginChild("StatsChild", imgui.ImVec2(0, 300), true) then
for player, stats in pairs(playerStats) do
imgui.Text("Игрок: " .. player)
imgui.SameLine(200)
imgui.Text("Срабатываний: " .. stats.count)
imgui.SameLine(350)
imgui.Text("Последний раз: " .. stats.lastSeen)
imgui.Separator()
end
imgui.EndChild()
end
if imgui.Button("Очистить статистику") then
playerStats = {}
end
imgui.EndTabItem()
end
if imgui.BeginTabItem("Telegram") then
imgui.Text("Настройки Telegram бота:")
imgui.Separator()
imgui.Text("Токен бота:")
imgui.InputText("##token", telegramToken, sizeof(telegramToken), imgui.InputTextFlags.Password)
imgui.Text("Chat ID:")
imgui.InputText("##chatid", telegramChatID, sizeof(telegramChatID))
imgui.Spacing()
if imgui.Button("Сохранить") then
config.settings.telegramToken = str(telegramToken)
config.settings.telegramChatID = str(telegramChatID)
saveConfig()
sampAddChatMessage("[МОНИТОРИНГ] Настройки Telegram сохранены!", -1)
end
imgui.Spacing()
if imgui.Button("Тест уведомления") then
sendToTelegram("TestPlayer", "тест", "Это тестовое сообщение от бота мониторинга", "test")
end
imgui.Spacing()
imgui.Separator()
imgui.TextWrapped("Инструкция:\n1. Создайте бота через @BotFather\n2. Получите токен бота\n3. Добавьте бота в чат и получите Chat ID\n4. Введите данные выше\n5. Нажмите 'Тест уведомления' для проверки")
imgui.EndTabItem()
end
imgui.EndTabBar()
end
imgui.End()
end
end
)
-- Обработка сообщений в чате (НОВОЕ!)
function sampev.onShowDialog(dialogId, style, title, button1, button2, text)
return
end
function sampev.onPlayerChatMessage(playerId, message)
if config.settings.monitorChat then
local playerName = sampGetPlayerNickname(playerId)
if playerName then
checkMessage(playerName .. ": " .. message, "chat")
end
end
end
-- Обработка сообщений сервера
function sampev.onServerMessage(color, text)
if config.settings.monitorServer then
checkMessage(text, "server")
end
end
-- Обработка команды /skam
function sampev.onSendCommand(command)
if command:lower() == "/skam" then
renderWindow[0] = not renderWindow[0]
if renderWindow[0] then
sampSetCursorMode(3) -- CURSOR_MODE_USER
sampAddChatMessage("[МОНИТОРИНГ] Меню открыто.", -1)
else
sampSetCursorMode(0) -- CURSOR_MODE_PLAYER
sampAddChatMessage("[МОНИТОРИНГ] Меню закрыто.", -1)
end
return false
end
end
-- Главная функция
function main()
while not isSampAvailable() do wait(0) end
loadLogs()
local message = "[МОНИТОРИНГ v2.1] Скрипт загружен! Команда: {FFFF00}/skam"
print(string.format("[DEBUG] Отправка в чат: %s", message))
sampAddChatMessage(message, -1)
wait(-1)
end