local event = require("event") local modemConnection = require("modemConnection") local ecs = require("ECSAPI") local fs = require("filesystem") local serialization = require("serialization") local buffer = require("doubleBuffering") local context = require("context") local image = require("image") local unicode = require("unicode") local component = require("component") local computer = require("computer") ------------------------------------------------------------------------------------------------------------------------------- if not component.isAvailable("modem") then ecs.error("This program requires wireless modem to work!"); return end local colors = { leftBar = 0x262626, leftBarSelection = 0x00A8FF, leftBarAlternative = 0x383838, leftBarText = 0xFFFFFF, leftBarSelectionText = 0xFFFFFF, leftBarSearchButton = 0x555555, leftBarSearchButtonText = 0xFFFFFF, scrollBar = 0xCCCCCC, scrollBarPipe = 0x666666, topBar = 0xEEEEEE, topBarName = 0x000000, topBarAddress = 0x555555, topMenu = 0xFFFFFF, chatZone = 0xFFFFFF, senderCloudColor = 0x3392FF, senderCloudTextColor = 0xFFFFFF, yourCloudColor = 0x55BBFF, yourCloudTextColor = 0xFFFFFF, systemMessageColor = 0x555555, sendButtonColor = 0x555555, sendButtonTextColor = 0xFFFFFF, messageInputBarColor = 0xEEEEEE, messageInputBarInputBackgroundColor = 0xFFFFFF, messsageInputBarButtonColor = 0x3392FF, messsageInputBarButtonTextColor = 0xFFFFFF, messsageInputBarTextColor = 0x262626, } local chatHistory = {} local avatars = {} local port = 899 local modem = component.modem modem.open(port) ------------------------------------------------------------------------------------------------------------------------------- local pathToApplication = "/MineOS/Applications/Chat.app/" local pathToSaveSendedFiles = "/MineOS/Downloads/" local contactsAvatarsPath = pathToApplication .. "Resources/Avatars/" local personalAvatarPath = contactsAvatarsPath .. "MyAvatar.pic" local chatHistoryPath = "MineOS/System/Chat/History.cfg" local avatarWidthLimit = 6 local avatarHeightLimit = 3 local currentChatID = 1 local currentChatMessage = 1 local currentMessageText buffer.start() local messageInputHeight = 5 local leftBarHeight = buffer.screen.height - 9 local leftBarWidth = math.floor(buffer.screen.width * 0.2) local chatZoneWidth = buffer.screen.width - leftBarWidth local heightOfTopBar = 2 + avatarHeightLimit local yLeftBar = 2 + heightOfTopBar local chatZoneX = leftBarWidth + 1 local bottom local chatZoneHeight = buffer.screen.height - yLeftBar - messageInputHeight + 1 local cloudWidth = chatZoneWidth - 2 * (avatarWidthLimit + 9) local cloudTextWidth = cloudWidth - 4 local yMessageInput = buffer.screen.height - messageInputHeight + 1 local sendButtonWidth = 7 local messageInputWidth = chatZoneWidth - sendButtonWidth - 6 ------------------------------------------------------------------------------------------------------------------------------- --Объекты для тача local obj = {} local function newObj(class, name, ...) obj[class] = obj[class] or {} obj[class][name] = {...} end local function saveChatHistory() fs.makeDirectory(fs.path(chatHistoryPath) or "") local file = io.open(chatHistoryPath, "w") file:write(serialization.serialize(chatHistory)) file:close() end local function loadChatHistory() if fs.exists(chatHistoryPath) then local file = io.open(chatHistoryPath, "r") chatHistory = serialization.unserialize(file:read("*a")) file:close() else chatHistory = {myName = "Аноним №" .. math.random(1, 1000)} saveChatHistory() end end local function loadAvatarFromFile(path) local avatar = image.load(path) local widthDifference = avatar.width - avatarWidthLimit local heightDifference = avatar.height - avatarHeightLimit if widthDifference > 0 then avatar = image.crop(avatar, "fromRight", widthDifference) end if heightDifference > 0 then avatar = image.crop(avatar, "fromBottom", heightDifference) end return avatar end local function loadPersonalAvatar() avatars.personal = loadAvatarFromFile(personalAvatarPath) end local function loadContactAvatar(ID) avatars.contact = loadAvatarFromFile(contactsAvatarsPath .. ID .. ".pic") end local function saveContactAvatar(ID, data) local file = io.open(contactsAvatarsPath .. ID .. ".pic", "w") file:write(data) file:close() end local function switchToContact(ID) currentChatID = ID currentChatMessage = #chatHistory[currentChatID] loadContactAvatar(currentChatID) chatHistory[currentChatID].unreadedMessages = nil end local function drawLeftBar() buffer.square(1, yLeftBar, leftBarWidth, leftBarHeight, colors.leftBar, 0xFFFFFF, " ") local howMuchContactsCanBeShown = math.floor(leftBarHeight / 3) obj.Contacts = {} local yPos = yLeftBar local counter = 1 local text, textColor for i = 1, #chatHistory do textColor = colors.leftBarText --Рисуем подложку if i == currentChatID then buffer.square(1, yPos, leftBarWidth, 3, colors.leftBarSelection, 0xFFFFFF, " ") textColor = 0xFFFFFF elseif counter % 2 ~= 0 then buffer.square(1, yPos, leftBarWidth, 3, colors.leftBarAlternative, 0xFFFFFF, " ") end --Создаем объекты для клика newObj("Contacts", i, 1, yPos, leftBarWidth, yPos + 2) --Рендерим корректное имя text = chatHistory[i].name or address text = ecs.stringLimit("end", text, leftBarWidth - 4) --Рисуем имя yPos = yPos + 1 buffer.text(2, yPos, textColor, text) --Если имеются непрочитанные сообщения, то показать их if chatHistory[i].unreadedMessages then local stringCount = tostring(chatHistory[i].unreadedMessages) local stringCountLength = unicode.len(stringCount) local x = leftBarWidth - 3 - stringCountLength buffer.square(x, yPos, stringCountLength + 2, 1, colors.leftBarText, 0xFFFFFF, " ") buffer.text(x + 1, yPos, colors.leftBar, stringCount) end yPos = yPos + 2 counter = counter + 1 if counter > howMuchContactsCanBeShown or yPos > buffer.screen.height then break end end --Кнопочка поиска юзеров obj.search = {buffer.button(1, buffer.screen.height - 2, leftBarWidth, 3, colors.leftBarSearchButton, colors.leftBarSearchButtonText, "Поиск")} end local function drawTopBar() buffer.square(1, 2, buffer.screen.width, heightOfTopBar, colors.topBar, 0xFFFFFF, " ") buffer.image(3, 3, avatars.personal) buffer.text(11, 3, colors.topBarName, chatHistory.myName) buffer.text(11, 4, colors.topBarAddress, modemConnection.localAddress) end local function drawTopMenu() obj.TopMenu = buffer.menu(1, 1, buffer.screen.width, colors.topMenu, 0, {"Чат", 0x000099}, {"История", 0x000000}, {"О программе", 0x000000}) end local function drawEmptyCloud(x, y, cloudWidth, cloudHeight, cloudColor, fromYou) local upperPixel = "▀" local lowerPixel = "▄" --Рисуем финтифлюшечки if not fromYou then buffer.set(x, y - cloudHeight + 2, colors.chatZone, cloudColor, upperPixel) buffer.set(x + 1, y - cloudHeight + 2, cloudColor, 0xFFFFFF, " ") x = x + 2 else buffer.set(x + cloudWidth + 3, y - cloudHeight + 2, colors.chatZone, cloudColor, upperPixel) buffer.set(x + cloudWidth + 2, y - cloudHeight + 2, cloudColor, 0xFFFFFF, " ") end --Заполняшечки buffer.square(x + 1, y - cloudHeight + 1, cloudWidth, cloudHeight, cloudColor, 0xFFFFFF, " ") buffer.square(x, y - cloudHeight + 2, cloudWidth + 2, cloudHeight - 2, cloudColor, 0xFFFFFF, " ") --Сгругленные краешки buffer.set(x, y - cloudHeight + 1, colors.chatZone, cloudColor, lowerPixel) buffer.set(x + cloudWidth + 1, y - cloudHeight + 1, colors.chatZone, cloudColor, lowerPixel) buffer.set(x, y, colors.chatZone, cloudColor, upperPixel) buffer.set(x + cloudWidth + 1, y, colors.chatZone, cloudColor, upperPixel) return y - cloudHeight + 1 end local function drawTextCloud(x, y, cloudColor, textColor, fromYou, text) local y = drawEmptyCloud(x, y, cloudTextWidth, #text + 2, cloudColor, fromYou) x = fromYou and x + 2 or x + 4 for i = 1, #text do buffer.text(x, y + i, textColor, text[i]) end return y end local function drawFileCloud(x, y, cloudColor, textColor, fromYou, fileName) local y = drawEmptyCloud(x, y, 14, 8, cloudColor, fromYou) x = fromYou and x + 2 or x + 4 ecs.drawOSIcon(x, y + 1, fileName, true, textColor) return y end local function stringWrap(text, limit) local strings = {} local textLength = unicode.len(text) local subFrom = 1 while subFrom <= textLength do table.insert(strings, unicode.sub(text, subFrom, subFrom + limit - 1)) subFrom = subFrom + limit end return strings end local function drawChat() local x, y = chatZoneX, yLeftBar buffer.square(x, y, chatZoneWidth, chatZoneHeight, colors.chatZone, 0xFFFFFF, " ") --Если отстутствуют контакты, то отобразить стартовое сообщение if not chatHistory[currentChatID] then local text = ecs.stringLimit("start", "Добавьте контакты с помощью кнопки \"Поиск\"", chatZoneWidth - 2) local x, y = math.floor(chatZoneX + chatZoneWidth / 2 - unicode.len(text) / 2), math.floor(yLeftBar + chatZoneHeight / 2) buffer.text(x, y, 0x555555, text) return end -- Ставим ограничение отрисовки буфера, чтобы облачка сообщений не ебошили -- За края верхней зоны чатзоны, ну ты понял, да? buffer.setDrawLimit(x, y, chatZoneWidth, chatZoneHeight) -- Стартовая точка y = buffer.screen.height - messageInputHeight - 1 local xYou, xSender = x + 2, buffer.screen.width - 8 -- Отрисовка облачков for i = currentChatMessage, 1, -1 do --Если не указан тип сообщения, то ренедрим дефолтные облачка if not chatHistory[currentChatID][i].type then --Если сообщенька от тебя, то цвет меняем if chatHistory[currentChatID][i].fromYou then y = drawTextCloud(xSender - cloudWidth - 2, y, colors.yourCloudColor, colors.yourCloudTextColor, chatHistory[currentChatID][i].fromYou, stringWrap(chatHistory[currentChatID][i].message, cloudTextWidth - 2)) buffer.image(xSender, y, avatars.personal) else y = drawTextCloud(xYou + 8, y, colors.senderCloudColor, colors.senderCloudTextColor, chatHistory[currentChatID][i].fromYou, stringWrap(chatHistory[currentChatID][i].message, cloudTextWidth)) buffer.image(xYou, y, avatars.contact) end --Если сообщение имеет тип "Файл" elseif chatHistory[currentChatID][i].type == "file" then if chatHistory[currentChatID][i].fromYou then y = drawFileCloud(xSender - 20, y, colors.yourCloudColor, colors.yourCloudTextColor, chatHistory[currentChatID][i].fromYou, chatHistory[currentChatID][i].message) buffer.image(xSender, y, avatars.personal) else y = drawFileCloud(xYou + 8, y, colors.senderCloudColor, colors.senderCloudTextColor, chatHistory[currentChatID][i].fromYou, chatHistory[currentChatID][i].message) buffer.image(xYou, y, avatars.contact) end else for i = chatZoneX, buffer.screen.width - 2 do buffer.set(i, y, colors.chatZone, colors.systemMessageColor, "─") end local x = math.floor(chatZoneX + (chatZoneWidth - 2) / 2 - unicode.len(chatHistory[currentChatID][i].message) / 2) buffer.text(x, y, colors.systemMessageColor, " " .. chatHistory[currentChatID][i].message .. " ") end y = y - 2 if y <= yLeftBar then break end end -- Убираем ограничение отроисовки буфера buffer.resetDrawLimit() buffer.scrollBar(buffer.screen.width, yLeftBar, 1, chatZoneHeight, #chatHistory[currentChatID], currentChatMessage, colors.scrollBar, colors.scrollBarPipe) end local function drawMessageInputBar() local x, y = chatZoneX, yMessageInput buffer.square(x, y, chatZoneWidth, messageInputHeight, colors.messageInputBarColor, 0xFFFFFF, " ") y = y + 1 buffer.square(x + 2, y, messageInputWidth, 3, colors.messageInputBarInputBackgroundColor, 0xFFFFFF, " ") buffer.text(x + 3, y + 1, colors.messsageInputBarTextColor, ecs.stringLimit("start", currentMessageText or "Введите сообщение", messageInputWidth - 2)) obj.send = {buffer.button(chatZoneX + messageInputWidth + 4, y, sendButtonWidth, 3, colors.sendButtonColor, colors.sendButtonTextColor, "⇪")} end local function drawAll(force) drawTopBar() drawLeftBar() drawTopMenu() drawChat() drawMessageInputBar() buffer.draw(force) end local function scrollChat(direction) if direction == 1 then if currentChatMessage > 1 then currentChatMessage = currentChatMessage - 1 drawChat() drawMessageInputBar() buffer.draw() end else if currentChatMessage < #chatHistory[currentChatID] then currentChatMessage = currentChatMessage + 1 drawChat() drawMessageInputBar() buffer.draw() end end end local function addMessageToArray(ID, type, fromYou, message) table.insert(chatHistory[ID], {type = type, fromYou = fromYou, message = message}) saveChatHistory() end local function sendMessage(type, message) modem.send(chatHistory[currentChatID].address, port, "HereIsMessageToYou", type, message) addMessageToArray(currentChatID, nil, true, currentMessageText) currentChatMessage = #chatHistory[currentChatID] currentMessageText = nil end local function checkAddressExists(address) for i = 1, #chatHistory do if chatHistory[i].address == address then return true end end return false end local function addNewContact(address, name, avatarData) if not checkAddressExists(address) then table.insert(chatHistory, { address = address, name = name, { type = "system", message = "Здесь будет показана история чата" } }) saveChatHistory() saveContactAvatar(#chatHistory, avatarData) end end local function askForAddToContacts(address) --Загружаем авку local file = io.open(personalAvatarPath, "r") local avatarData = file:read("*a") file:close() --Отсылаем свое имечко и аватарку modem.send(address, port, "AddMeToContactsPlease", chatHistory.myName, avatarData) end local function getNameAndIDOfAddress(address) for i = 1, #chatHistory do if chatHistory[i].address == address then return chatHistory[i].name, i end end return nil, nil end local function autoScroll() --Если мы никуда не скроллили и находимся в конце истории чата с этим юзером --То автоматически проскроллить на конец if currentChatMessage == (#chatHistory[currentChatID] - 1) then currentChatMessage = #chatHistory[currentChatID] end end local function receiveFile(remoteAddress, fileName) --Чекаем, есть ли он в контактах if checkAddressExists(remoteAddress) then --Создаем директорию под файлики, а то мало ли fs.makeDirectory(pathToSaveSendedFiles) --Получаем имя отправителя из контактов local senderName, senderID = getNameAndIDOfAddress(remoteAddress) --Открываем файл для записи local file = io.open(pathToSaveSendedFiles .. fileName, "w") --Запоминаем пиксели под окошком прогресса local oldPixels = ecs.progressWindow("auto", "auto", 40, 0, "Прием файла", true) --Начинаем ожидать беспроводных сообщений в течение 10 секунд while true do local fileReceiveEvent = { event.pull(10, "modem_message") } --Это сообщение несет в себе процентаж передачи и сами данные пакета if fileReceiveEvent[6] == "FILESEND" then --Рисуем окошко прогресса ecs.progressWindow("auto", "auto", 40, fileReceiveEvent[7], "Прием файла") file:write(fileReceiveEvent[8]) --Если нам присылают сообщение о завершении передачи, то закрываем файл elseif fileReceiveEvent[6] == "FILESENDEND" then ecs.progressWindow("auto", "auto", 40, 100, "Прием файла") file:close() ecs.drawOldPixels(oldPixels) --Вставляем сообщение с файликом-иконочкой addMessageToArray(senderID, "file", nil, fileName) autoScroll() drawAll() --Выдаем окошечко о том, что файл успешно передан ecs.universalWindow("auto", "auto", 30, 0x262626, true, {"EmptyLine"}, {"CenterText", ecs.colors.orange, "Прием данных завершен"}, {"EmptyLine"}, {"CenterText", 0xFFFFFF, "Файл от " .. senderName .. " сохранен как"}, {"CenterText", 0xFFFFFF, "\"" .. pathToSaveSendedFiles .. fileName .. "\""}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0x262626, "OK"}} ) break --Если не получали в течение указанного промежутка сообщений, то выдать сообщение об ошибке и удалить файл elseif not fileReceiveEvent[1] then file:close() ecs.drawOldPixels(oldPixels) fs.remove(pathToSaveSendedFiles .. fileName) ecs.error("Ошибка при передаче файла: клиент не отвечает") drawAll() break end end end end --Обработчик сообщений local function dro4er(_, localAddress, remoteAddress, remotePort, distance, ...) local messages = { ... } if remotePort == port then if messages[1] == "AddMeToContactsPlease" then if modemConnection.remoteAddress and modemConnection.remoteAddress == remoteAddress then -- ecs.error("Сообщение о добавлении получил, адрес: " .. modemConnection.remoteAddress .. ", имя:" .. messages[2] .. ", авка: " .. type(messages[3])) --Добавляем пидорка к себе в контакты addNewContact(modemConnection.remoteAddress, messages[2], messages[3]) --Просим того пидорка, чтобы он добавил нас к себе в контакты askForAddToContacts(modemConnection.remoteAddress) --Чтобы не было всяких соблазнов! modemConnection.availableUsers = {} modemConnection.remoteAddress = nil --Переключаемся на добавленный контакт switchToContact(#chatHistory) drawAll() end --Если какой-то чувак просит нас принять файл elseif messages[1] == "FAYLPRIMI" then receiveFile(remoteAddress, messages[2]) elseif messages[1] == "HereIsMessageToYou" then for i = 1, #chatHistory do --Если в массиве истории чата найден юзер, отославший такое сообщение if chatHistory[i].address == remoteAddress then --То вставляем само сообщение в историю чата addMessageToArray(i, messages[2], nil, messages[3]) --Если текущая открытая история чата является именно вот этой, с этим отправителем if currentChatID == i then autoScroll() --Обязательно отрисовываем измененную историю чата с этим отправителем drawChat() buffer.draw() --Увеличиваем количество непрочитанных сообщений от отправителя else chatHistory[i].unreadedMessages = chatHistory[i].unreadedMessages and chatHistory[i].unreadedMessages + 1 or 1 drawLeftBar() buffer.draw() end --Бип! computer.beep(1700) --А это небольшой костыльчик, чтобы не сбивался цвет курсора Term API component.gpu.setBackground(colors.messageInputBarInputBackgroundColor) component.gpu.setForeground(colors.messsageInputBarTextColor) break end end end end end local function enableDro4er() event.listen("modem_message", dro4er) end local function disableDro4er() event.ignore("modem_message", dro4er) end local function deleteAvatar(ID) fs.remove(contactsAvatarsPath .. ID .. ".pic") end local function clearChatHistory() for i = 1, #chatHistory do deleteAvatar(i) chatHistory[i] = nil end saveChatHistory() end local function sendFile(path) local data = ecs.universalWindow("auto", "auto", 30, 0x262626, true, {"EmptyLine"}, {"CenterText", ecs.colors.orange, "Отправить файл"}, {"EmptyLine"}, {"Input", 0xFFFFFF, ecs.colors.orange, "Путь"}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0x262626, "OK"}, {0x666666, 0xffffff, "Отмена"}} ) if data[2] == "OK" then if fs.exists(data[1]) then --Отправляем сообщение о том, что мы собираемся отправить файл modem.send(chatHistory[currentChatID].address, port, "FAYLPRIMI", fs.name(data[1])) --Открываем файл и отправляем его по количеству пакетов local maxPacketSize = modem.maxPacketSize() - 32 local file = io.open(data[1], "rb") local fileSize = fs.size(data[1]) local percent = 0 local sendedBytes = 0 local dataToSend while true do dataToSend = file:read(maxPacketSize) if dataToSend then modem.send(chatHistory[currentChatID].address, port, "FILESEND", percent, dataToSend) sendedBytes = sendedBytes + maxPacketSize percent = math.floor(sendedBytes / fileSize * 100) else break end end file:close() --Репортуем об окончании передачи файла modem.send(chatHistory[currentChatID].address, port, "FILESENDEND") --Вставляем в чат инфу об обтправленном файле addMessageToArray(currentChatID, "file", true, fs.name(data[1])) autoScroll() drawAll() else ecs.error("Файл \"" .. data[1] .. "\" не существует.") end end end local function deleteContact(ID) table.remove(chatHistory, ID) deleteAvatar(ID) if #chatHistory > 0 then switchToContact(1) else currentChatID = 1 currentChatMessage = 1 end saveChatHistory() end ------------------------------------------------------------------------------------------------------------------------------- --Отключаем принудительное завершение программы ecs.disableInterrupting() --Загружаем историю чата и свою аватарку loadChatHistory() loadPersonalAvatar() --Если история не пуста, то переключаемся на указанный контакт if chatHistory[currentChatID] then switchToContact(currentChatID) end --Включаем прием данных по модему для подключения modemConnection.startReceivingData() --Отсылаем всем модемам сигнал о том, чтобы они удалили нас из своего списка modemConnection.disconnect() --Отправляем свои данные, чтобы нас заново внесли в список modemConnection.sendPersonalData() --Активируем прием сообщений чата enableDro4er() --Рисуем весь интерфейс drawAll() ------------------------------------------------------------------------------------------------------------------------------- while true do local e = { event.pull() } if e[1] == "touch" then -- Клик на поле ввода сообщения if #chatHistory > 0 and ecs.clickedAtArea(e[3], e[4], chatZoneX + 2, yMessageInput, chatZoneX + messageInputWidth + 2, yMessageInput + 3) then local text = ecs.inputText(chatZoneX + 3, yMessageInput + 2, messageInputWidth - 2, currentMessageText, colors.messageInputBarInputBackgroundColor, colors.messsageInputBarTextColor) if text ~= nil and text ~= "" then currentMessageText = text sendMessage(nil, currentMessageText) buffer.square(chatZoneX + 2, yMessageInput + 1, messageInputWidth, 3, colors.messageInputBarInputBackgroundColor, 0xFFFFFF, " ") buffer.draw() drawMessageInputBar() drawChat() buffer.draw() end -- Жмякаем на кнопочку "Отправить" elseif #chatHistory > 0 and ecs.clickedAtArea(e[3], e[4], obj.send[1], obj.send[2], obj.send[3], obj.send[4]) then buffer.button(obj.send[1], obj.send[2], sendButtonWidth, 3, colors.sendButtonTextColor, colors.sendButtonColor, "⇪") buffer.draw() os.sleep(0.2) drawMessageInputBar() buffer.draw() sendFile() -- Кнопа поиска elseif ecs.clickedAtArea(e[3], e[4], obj.search[1], obj.search[2], obj.search[3], obj.search[4]) then buffer.button(obj.search[1], obj.search[2], leftBarWidth, 3, colors.leftBarSearchButtonText, colors.leftBarSearchButton, "Поиск") buffer.draw() os.sleep(0.2) modemConnection.search() --Если после поиска мы подключились к какому-либо адресу if modemConnection.remoteAddress then --Просим адрес добавить нас в свой список контактов askForAddToContacts(modemConnection.remoteAddress) end drawAll(true) end --Клик на контакты for key in pairs(obj.Contacts) do if ecs.clickedAtArea(e[3], e[4], obj.Contacts[key][1], obj.Contacts[key][2], obj.Contacts[key][3], obj.Contacts[key][4]) then if e[5] == 0 then switchToContact(key) drawAll() else local action = context.menu(e[3], e[4], {"Переименовать"}, {"Удалить"}) if action == "Переименовать" then local data = ecs.universalWindow("auto", "auto", 30, 0x262626, true, {"EmptyLine"}, {"CenterText", ecs.colors.orange, "Переименовать контакт"}, {"EmptyLine"}, {"Input", 0xFFFFFF, ecs.colors.orange, chatHistory[key].name}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0x262626, "OK"}, {0x666666, 0xffffff, "Отмена"}} ) if data[2] == "OK" then chatHistory[key].name = data[1] or chatHistory[key].name drawAll() end elseif action == "Удалить" then deleteContact(key) drawAll() end end break end end for key in pairs(obj.TopMenu) do if ecs.clickedAtArea(e[3], e[4], obj.TopMenu[key][1], obj.TopMenu[key][2], obj.TopMenu[key][3],obj.TopMenu[key][4]) then buffer.button(obj.TopMenu[key][1] - 1, obj.TopMenu[key][2], unicode.len(key) + 2, 1, ecs.colors.blue, 0xFFFFFF, key) buffer.draw() local action if key == "Чат" then action = context.menu(obj.TopMenu[key][1] - 1, obj.TopMenu[key][2] + 1, {"Изменить имя"}, {"Изменить аватар"}, {"Очистить историю"},"-", {"Выход"}) elseif key == "О программе" then ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", ecs.colors.orange, "Chat v1.0"}, {"EmptyLine"}, {"CenterText", 0xFFFFFF, "Автор:"}, {"CenterText", 0xBBBBBB, "Тимофеев Игорь"}, {"CenterText", 0xBBBBBB, "vk.com/id7799889"}, {"EmptyLine"}, {"CenterText", 0xFFFFFF, "Тестеры:"}, {"CenterText", 0xBBBBBB, "Егор Палиев"}, {"CenterText", 0xBBBBBB, "vk.com/mrherobrine"}, {"CenterText", 0xBBBBBB, "Максим Хлебников"}, {"CenterText", 0xBBBBBB, "vk.com/mskalash"}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0x262626, "OK"}} ) end if action == "Выход" then disableDro4er() modemConnection.stopReceivingData() modemConnection.disconnect() ecs.enableInterrupting() modem.close(port) buffer.clear() ecs.prepareToExit() return elseif action == "Очистить историю" then clearChatHistory() drawAll() end drawTopMenu() buffer.draw() break end end elseif e[1] == "scroll" then if #chatHistory > 0 and ecs.clickedAtArea(e[3], e[4], chatZoneX, yLeftBar, chatZoneX + chatZoneWidth - 1, yLeftBar + chatZoneHeight - 1) then scrollChat(e[5]) end end end