diff --git a/Applications/IRC/Icon.pic b/Applications/IRC/Icon.pic new file mode 100644 index 00000000..04062509 Binary files /dev/null and b/Applications/IRC/Icon.pic differ diff --git a/Applications/IRC/Main.lua b/Applications/IRC/Main.lua new file mode 100644 index 00000000..053f2a5e --- /dev/null +++ b/Applications/IRC/Main.lua @@ -0,0 +1,775 @@ +local component = require("component") +local computer = require("computer") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local color = require("color") +local filesystem = require("filesystem") +local unicode = require("unicode") +local MineOSCore = require("MineOSCore") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") + +------------------------------------------------------------------------------- + +local socketHandle +local socketConnectDelay = 1 +local socketReadDelay = 1 + +local channelUsersList = {operators = {}, voiced = {}, default = {}} +local channelUsersListUpdatingFinished = true + +local userZoneMaxWidth = 26 +local chatTimeWidth = 5 + +local selectedItem + +local socketUsername = "Socket " + +local systemNames = { + ["NickServ"] = true, + ["ChanServ"] = true, + ["DerpServ"] = true, + [socketUsername] = true, +} + +local colorScheme = { + noticeMessageSender = 0x0049BF, + noticeMessageText = 0x0092FF, + actionMessageSender = 0xCC9200, + actionMessageText = 0xFFB600, + myMessageSender = 0x2D2D2D, + myMessageText = 0x969696, + otherMessageSender = 0x2D2D2D, + otherMessageText = 0x5A5A5A, + channelDataMessageSender = 0x696969, + channelDataMessageText = 0xB4B4B4, +} + +local applicationPath = MineOSPaths.applicationData .. "IRC/" +local configPath = applicationPath .. "Config.cfg" +local historyPath = applicationPath .. "History.cfg" + +local config = { + server = "irc.esper.net", + port = 6667, + username = "", + password = "", + historyLimit = 50, +} + +local history = { + ["#cc.ru"] = {}, + ["#oc"] = {}, + [socketUsername] = {}, + ["NickServ"] = {}, + ["ECS"] = {}, +} + +if filesystem.exists(configPath) then + config = table.fromFile(configPath) +end + +if filesystem.exists(historyPath) then + history = table.fromFile(historyPath) +end + +------------------------------------------------------------------------------- + +local mainContainer, window = MineOSInterface.addWindow(GUI.filledWindow(1, 1, 110, 27, 0x2D2D2D)) + +local leftPanel = window:addChild(GUI.panel(1, 1, 20, 1, 0x2D2D2D)) +local leftLayout = window:addChild(GUI.layout(1, 4, leftPanel.width, 1, 1, 1)) +leftLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) +leftLayout:setMargin(1, 1, 0, 0) + +local channelsText = leftLayout:addChild(GUI.text(1, 1, 0xE1E1E1, " Channels")) +local channelsList = leftLayout:addChild(GUI.list(1, 1, leftPanel.width, 1, 1, 0, 0x2D2D2D, 0x696969, 0x2D2D2D, 0x696969, 0x3366CC, 0xE1E1E1, false)) + +local usersText = leftLayout:addChild(GUI.text(1, 1, 0xE1E1E1, " Users")) +local usersList = leftLayout:addChild(GUI.list(1, 1, leftPanel.width, 1, 1, 0, 0x2D2D2D, 0x696969, 0x2D2D2D, 0x696969, 0x3366CC, 0xE1E1E1, false)) + +local systemText = leftLayout:addChild(GUI.text(1, 1, 0xE1E1E1, " System")) +local systemList = leftLayout:addChild(GUI.list(1, 1, leftPanel.width, 1, 1, 0, 0x2D2D2D, 0x696969, 0x2D2D2D, 0x696969, 0x3366CC, 0xE1E1E1, false)) + +local contactButtonWidth, contactButtonHeight = 10, 3 +local contactLayout = window:addChild(GUI.layout(1, 1, leftPanel.width, contactButtonHeight, 1, 1)) +contactLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP) +contactLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) +contactLayout:setSpacing(1, 1, 0) + +local contactAddButton = contactLayout:addChild(GUI.button(1, 1, contactButtonWidth, contactButtonHeight, 0x3C3C3C, 0xA5A5A5, 0x878787, 0xB4B4B4, "+")) +local contactRemoveButton = contactLayout:addChild(GUI.button(1, 1, contactButtonWidth, contactButtonHeight, 0x4B4B4B, 0xA5A5A5, 0x878787, 0xB4B4B4, "-")) +contactRemoveButton.colors.disabled.background = 0x4B4B4B +contactRemoveButton.colors.disabled.text = 0x696969 + +window.backgroundPanel.width = 18 +local rightLayout = window:addChild(GUI.layout(1, 1, window.backgroundPanel.width - 2, 1, 1, 1)) +rightLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) +rightLayout:setMargin(1, 1, 0, 0) +rightLayout:setSpacing(1, 1, 0) + +local chat = window:addChild(GUI.object(1, 1, 1, 1)) +chat.passScreenEvents = true + +local scrollBar = window:addChild(GUI.scrollBar(1, 1, 1, 1, 0xD2D2D2, 0x878787, 1, 1, 1, 1, 1, true)) + +local chatInputLayout = window:addChild(GUI.layout(1, 1, 1, 3, 1, 1)) +chatInputLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) +chatInputLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) +chatInputLayout:setSpacing(1, 1, 0) + +local chatInput = chatInputLayout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x696969, 0xA5A5A5, 0xE1E1E1, 0x2D2D2D, "", "Type message here")) +chatInput.historyEnabled = true + +local chatComboBox = chatInputLayout:addChild(GUI.comboBox(1, 1, 14, 3, 0xD2D2D2, 0x696969, 0xC3C3C3, 0x969696)) +chatComboBox.dropDownMenu.itemHeight = 1 +chatComboBox:addItem("/msg") +chatComboBox:addItem("/me") +chatComboBox:addItem("/notice") +chatComboBox:addItem("/ctcp") +chatComboBox:addItem("/raw") + +local backgroundContainer = window:addChild(GUI.container(1, 1, 1, 1)) +local backgroundPanel = backgroundContainer:addChild(GUI.panel(1, 1, 1, 1, 0xF0F0F0)) +local backgroundLayout = backgroundContainer:addChild(GUI.layout(1, 1, 1, 1, 1, 1)) +backgroundLayout.hidden = true + +local loginServerLayout = GUI.layout(1, 1, 36, 3, 1, 1) +loginServerLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) +loginServerLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) + +local loginServerInput = loginServerLayout:addChild(GUI.input(1, 1, 27, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, config.server or "", "Server")) +local loginPortInput = loginServerLayout:addChild(GUI.input(1, 1, loginServerLayout.width - loginServerInput.width - 1, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, config.port and tostring(config.port) or "", "Port")) + +local loginUsernameInput = GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, config.username or "", "Username") +local loginPasswordInput = GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, config.password or "", "Password") +local loginPasswordSwitchAndLabel = GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0xE1E1E1, 0xFFFFFF, 0xB4B4B4, "Log into NickServ:", true) +local loginSubmitButton = GUI.button(1, 1, 36, 3, 0xC3C3C3, 0xFFFFFF, 0x696969, 0xFFFFFF, "Connect") +local loginStatusText = GUI.text(1, 1, 0xA5A5A5, "") + +local addContactInput = GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, "", "User or chat name") +local addContactSubmitButton = GUI.button(1, 1, 36, 3, 0xB4B4B4, 0xFFFFFF, 0x969696, 0xB4B4B4, "Add contact") + +window.actionButtons.localX = 3 +window.actionButtons:moveToFront() + +local function getProperList(name) + return systemNames[name] and systemList or name:sub(1, 1) == "#" and channelsList or usersList +end + +local function getItemByText(name) + return getProperList(name):getItem(name) +end + +local function saveConfig() + table.toFile(configPath, config) +end + +local function socketWrite(data) + local success, result = pcall(socketHandle.write, data .. "\r\n") + if success then + return true, result + else + return false, result + end +end + +local function sendMessage(target, text, notice, ctcp) + socketWrite((notice and "NOTICE " or "PRIVMSG ") .. target .. (ctcp and " :\x01" or " :") .. text .. (ctcp and "\x01" or "")) +end + +local function status(text) + local state = text ~= nil + if text then + loginStatusText.text = text + loginStatusText:update() + end + + loginStatusText.hidden, loginServerLayout.hidden, loginUsernameInput.hidden, loginPasswordSwitchAndLabel.hidden, loginSubmitButton.hidden = not state, state, state, state, state + loginPasswordInput.hidden = state and true or not loginPasswordSwitchAndLabel.switch.state + + mainContainer:drawOnScreen() +end + +local function checkTextAndList(text, list) + text.hidden = #list.children == 0 + list.hidden = text.hidden + list.height = #list.children +end + +local function updateLeftLayout() + checkTextAndList(channelsText, channelsList) + checkTextAndList(usersText, usersList) + checkTextAndList(systemText, systemList) +end + +local function contactItemDraw(item) + local background = item.pressed and item.colors.pressed.background or item.colors.default.background + local foreground = item.pressed and item.colors.pressed.text or item.colors.default.text + + buffer.drawRectangle(item.x, item.y, item.width, item.height, background, foreground, " ") + + local y, textLimit = math.floor(item.y + item.height / 2) + if not item.pressed and item.unreadCount > 0 then + local tipString = tostring(item.unreadCount) + local tipWidth = #tipString + 2 + + buffer.drawRectangle(item.x + item.width - tipWidth - 1, y, tipWidth, 1, 0x3C3C3C, 0xE1E1E1, " ") + buffer.drawText(item.x + item.width - tipWidth, y, 0xE1E1E1, tipString) + + textLimit = item.width - 4 - tipWidth + else + textLimit = item.width - 4 + end + + buffer.drawText(item.x + 2, y, foreground, string.limit(item.text, textLimit, "right")) +end + +local function addContactItemToList(name) + if not history[name] then + history[name] = {} + end + + local list = getProperList(name) + local item = list:addItem(name) + + item.unreadCount = 0 + item.draw = contactItemDraw + + item.onTouch = function() + if list == channelsList then + usersList.selectedItem = nil + systemList.selectedItem = nil + elseif list == usersList then + channelsList.selectedItem = nil + systemList.selectedItem = nil + else + channelsList.selectedItem = nil + usersList.selectedItem = nil + end + + selectedItem = item + item.unreadCount = 0 + contactRemoveButton.disabled = systemNames[item.text] + rightLayout.cells[1][1].verticalMargin = 0 + + scrollBar.maximumValue = #history[name] + scrollBar.value = scrollBar.maximumValue + + rightLayout:removeChildren() + mainContainer:drawOnScreen() + + if socketHandle then + if list == channelsList then + if item.joined then + socketWrite("NAMES " .. name) + else + item.joined = true + socketWrite("JOIN " .. name) + end + end + end + end + + table.sort(list.children, function(a, b) return a.text:lower() < b.text:lower() end) + updateLeftLayout() + + return item +end + +local function addChatMessage(conversationName, text, sender) + if not history[conversationName] then + addContactItemToList(conversationName) + end + + text = text:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\'", "\\\'"):gsub("[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F]+", "") + + local message = { + text = text, + time = os.date("%H:%M", MineOSCore.time), + sender = sender + } + + table.insert(history[conversationName], message) + if #history[conversationName] > config.historyLimit then + table.remove(history[conversationName], 1) + end + + local item = getItemByText(conversationName) + if item == selectedItem then + scrollBar.maximumValue = #history[conversationName] + + if scrollBar.value == scrollBar.maximumValue - 1 then + scrollBar.value = scrollBar.maximumValue + end + elseif item.unreadCount < config.historyLimit then + item.unreadCount = item.unreadCount + 1 + end + + return message +end + +local function checkLoginInputs() + loginSubmitButton.disabled = #loginUsernameInput.text == 0 or #loginServerInput.text == 0 or not loginPortInput.text:match("^%d+$") +end + +chat.draw = function() + buffer.drawRectangle(chat.x, chat.y, chat.width, chat.height, 0xF0F0F0, colorScheme.otherMessageText, " ") + + if history[selectedItem.text] and #history[selectedItem.text] > 0 then + local y, userZoneWidth, messages = chat.y + chat.height - 2, 0, history[selectedItem.text] + + if not messages[scrollBar.value] then + GUI.alert(scrollBar.value, #messages) + return + end + + for i = scrollBar.value, 1, -1 do + userZoneWidth = math.max(userZoneWidth, unicode.len(messages[i].sender or selectedItem.text)) + end + userZoneWidth = math.min(userZoneWidth + chatTimeWidth + 4, userZoneMaxWidth) + + for i = chat.y, chat.y + chat.height - 1 do + local index = buffer.getIndex(chat.x + userZoneWidth - 1, i) + local background, foreground, symbol = buffer.rawGet(index) + + buffer.rawSet(index, background, 0xE1E1E1, "│") + end + + for i = scrollBar.value, 1, -1 do + local wrappedLines = string.wrap(messages[i].text, chat.width - userZoneWidth - 3) + + local senderColor, textColor + if messages[i].notice then + senderColor, textColor = colorScheme.noticeMessageSender, colorScheme.noticeMessageText + elseif messages[i].action then + senderColor, textColor = colorScheme.actionMessageSender, colorScheme.actionMessageText + elseif messages[i].channelAction then + senderColor, textColor = colorScheme.channelDataMessageSender, colorScheme.channelDataMessageText + elseif messages[i].sender == config.username then + senderColor, textColor = colorScheme.myMessageSender, colorScheme.myMessageText + else + senderColor, textColor = colorScheme.otherMessageSender, colorScheme.otherMessageText + end + + for j = #wrappedLines, 1, -1 do + buffer.drawText(chat.x + userZoneWidth + 1, y, textColor, wrappedLines[j]) + + y = y - 1 + if y < chat.y then + return + end + end + + local sender = messages[i].sender or selectedItem.text + local limited = string.limit(sender, userZoneWidth - chatTimeWidth - 4, "center") + buffer.drawText(chat.x + userZoneWidth - unicode.len(limited) - 2, y + 1, senderColor, limited) + buffer.drawText(chat.x + 1, y + 1, 0xC3C3C3, messages[i].time) + + y = y - 1 + end + end +end + +loginUsernameInput.onInputFinished = function() + checkLoginInputs() + mainContainer:drawOnScreen() +end + +loginPasswordInput.onInputFinished = loginUsernameInput.onInputFinished +loginServerInput.onInputFinished = loginUsernameInput.onInputFinished +loginPortInput.onInputFinished = loginUsernameInput.onInputFinished + +local function login() + backgroundLayout.hidden = false + + backgroundLayout:removeChildren() + backgroundLayout:addChild(loginServerLayout) + backgroundLayout:addChild(loginUsernameInput) + backgroundLayout:addChild(loginPasswordInput) + backgroundLayout:addChild(loginPasswordSwitchAndLabel) + backgroundLayout:addChild(loginSubmitButton) + backgroundLayout:addChild(loginStatusText) + + checkLoginInputs() + status() +end + +chatInput.onInputFinished = function() + if #chatInput.text > 0 then + if chatComboBox.selectedItem == 1 then + sendMessage(selectedItem.text, chatInput.text) + addChatMessage(selectedItem.text, chatInput.text, config.username) + elseif chatComboBox.selectedItem == 2 then + sendMessage(selectedItem.text, "ACTION " .. chatInput.text, false, true) + addChatMessage(selectedItem.text, chatInput.text, config.username, "!").action = true + elseif chatComboBox.selectedItem == 3 then + sendMessage(selectedItem.text, chatInput.text, true) + addChatMessage(selectedItem.text, chatInput.text, config.username, "!").notice = true + elseif chatComboBox.selectedItem == 3 then + sendMessage(selectedItem.text, chatInput.text, false, true) + addChatMessage(selectedItem.text, chatInput.text, config.username, "!").action = true + else + socketWrite(chatInput.text) + addChatMessage(selectedItem.text, chatInput.text, config.username) + end + + chatInput.text = "" + + mainContainer:drawOnScreen() + end +end + +local function getTotalHeight(layout) + local height = 0 + + for i = 1, #layout.children do + height = height + layout.children[i].height + (i < #layout.children and layout.cells[1][1].spacing or 0) + end + + return height +end + +local function addScrollEventHandler(layout) + layout.eventHandler = function(mainContainer, layout, e1, e2, e3, e4, e5) + if e1 == "scroll" then + if e5 > 0 then + if layout.cells[1][1].verticalMargin < 0 then + layout.cells[1][1].verticalMargin = layout.cells[1][1].verticalMargin + 1 + mainContainer:drawOnScreen() + end + else + if layout.cells[1][1].verticalMargin > -getTotalHeight(layout) + 1 then + layout.cells[1][1].verticalMargin = layout.cells[1][1].verticalMargin - 1 + mainContainer:drawOnScreen() + end + end + end + end +end + +addScrollEventHandler(leftLayout) +addScrollEventHandler(rightLayout) + +local function updateUsersLayoutFromList() + rightLayout:removeChildren() + + local function addCategory(field, name) + if #channelUsersList[field] > 0 then + rightLayout:addChild(GUI.object(1, 1, 1, 1)) + rightLayout:addChild(GUI.text(1, 1, 0xE1E1E1, name)).height = 2 + + table.sort(channelUsersList[field], function(a, b) return unicode.lower(a) < unicode.lower(b) end) + + for i = 1, #channelUsersList[field] do + rightLayout:addChild(GUI.adaptiveButton(1, 1, 0, 0, nil, 0x787878, nil, 0x4B4B4B, channelUsersList[field][i])) + end + end + end + + addCategory("operators", "Operators") + addCategory("voiced", "Voiced") + addCategory("default", "Users") +end + +local function addUserToList(name) + local firstSymbol = name:sub(1, 1) + local field = firstSymbol == "@" and "operators" or firstSymbol == "+" and "voiced" or "default" + + for i = 1, #channelUsersList[field] do + if channelUsersList[field][i] == name then + return true + end + end + + table.insert(channelUsersList[field], name) +end + +local function removeUserFromList(name) + for field in pairs(channelUsersList) do + for i = 1, #channelUsersList[field] do + if channelUsersList[field][i] == name then + table.remove(channelUsersList[field], i) + updateUsersLayoutFromList() + return true + end + end + end +end + +local function selectItem(item) + selectedItem = item + item.parent.selectedItem = item:indexOf() + item.onTouch() +end + +local oldUptime = 0 +chat.eventHandler = function(mainContainer, chat, e1, e2, e3, e4, e5) + if e1 == "scroll" then + if e5 > 0 then + if scrollBar.value > 1 then + scrollBar.value = scrollBar.value - 1 + mainContainer:drawOnScreen() + end + else + if scrollBar.value < scrollBar.maximumValue then + scrollBar.value = scrollBar.value + 1 + mainContainer:drawOnScreen() + end + end + elseif not e1 and socketHandle and computer.uptime() - oldUptime > socketReadDelay then + local data = "" + while true do + local success, result = pcall(socketHandle.read, math.huge) + if success then + if result and #result > 0 then + data = data .. result + elseif #data > 0 then + for line in data:gmatch("[^\r\n]+") do + addChatMessage(socketUsername, line) + + local lineWithoutDots = line:match("^:(.+)") + if lineWithoutDots then + local words = {} + for word in lineWithoutDots:gmatch("[^%s]+") do + table.insert(words, word) + end + + if #words >= 2 then + local beforeDots, afterDots = lineWithoutDots:match("^([^:]+):(.+)") + local username, address = words[1]:match("([^!]+)!([^!]+)") + + if words[2] == "NOTICE" then + if username then + if username == "NickServ" and afterDots then + if afterDots:match("You are now identified") then + backgroundContainer.hidden = true + elseif afterDots:match("Invalid password for") then + GUI.alert("Invalid password") + end + end + + addChatMessage(selectedItem.text, afterDots, username).notice = true + else + if afterDots and afterDots:match("Looking up your hostname...") then + socketWrite("USER " .. config.username .. " 0 * :" .. config.username .. "\r\nNICK " .. config.username) + end + end + elseif words[2] == "PRIVMSG" and username then + local conversationName = words[3]:sub(1, 1) == "#" and words[3] or username + + local ctcp = afterDots:match("^\1(.+)\1$") + if ctcp then + local command, data = ctcp:match("^([^%s]+)%s(.+)") + if ctcp == "VERSION" then + sendMessage(username, "VERSION MineOS IRC Client / OpenComputers (Lua 5.3)", true, true) + elseif ctcp == "TIME" then + sendMessage(username, "TIME " .. os.date(MineOSCore.time), true, true) + elseif command == "PING" then + sendMessage(username, "PING " .. data, true, true) + elseif command == "ACTION" then + addChatMessage(conversationName, username .. " " .. data, "*").action = true + else + message = "Unsupported CTCP command: " .. ctcp + end + -- Regular message + else + addChatMessage(conversationName, afterDots, username) + end + + -- computer.beep(2000, 0.05) + elseif words[2] == "JOIN" and username then + if selectedItem.text == words[3] then + if not addUserToList(username) then + updateUsersLayoutFromList() + end + end + + addChatMessage(words[3], username .. " (" .. address .. ") has joined the channel", "!").channelAction = true + elseif words[2] == "MODE" and username then + addChatMessage(words[3], username .. " sets " .. words[5] .. " mode to " .. words[4], "!").channelAction = true + elseif (words[2] == "QUIT" or words[2] == "PART") and username then + if removeUserFromList(username) and words[2] == "QUIT" then + addChatMessage(selectedItem.text, username .. " (" .. address .. ") has quit (" .. afterDots .. ")", "!").channelAction = true + end + + if words[2] == "PART" and username ~= config.username then + addChatMessage(words[3], username .. " (" .. address .. ") has left the channel", "!").channelAction = true + end + -- User list + elseif words[2] == "353" then + if channelUsersListUpdatingFinished then + channelUsersList.operators, channelUsersList.voiced, channelUsersList.default = {}, {}, {} + channelUsersListUpdatingFinished = false + end + + for username in afterDots:gmatch("[^%s]+") do + addUserToList(username) + end + -- User list finish + elseif words[2] == "366" then + channelUsersListUpdatingFinished = true + updateUsersLayoutFromList() + -- No password authorization + elseif words[2] == "001" then + if loginPasswordSwitchAndLabel.switch.state then + status("Waiting for login request...") + else + backgroundContainer.hidden = true + end + -- Nickname in use + elseif words[2] == "433" then + status() + GUI.alert("Nickname is already in use") + -- Channel topic + elseif words[2] == "332" then + addChatMessage(words[4], afterDots, ">").channelAction = true + -- Wait a while + elseif words[2] == "263" then + addChatMessage(selectedItem.text, afterDots, "!").channelAction = true + -- End of MOTD + elseif words[2] == "376" then + if loginPasswordSwitchAndLabel.switch.state then + status("Logging in...") + socketWrite("IDENTIFY " .. config.username .. " " .. config.password) + end + end + end + else + local ping = line:match("^PING( :.+)") + if ping then + socketWrite("PONG" .. ping) + end + end + end + + mainContainer:drawOnScreen() + break + else + break + end + else + GUI.alert("Failed to read data from socket: " .. tostring(result)) + break + end + end + + oldUptime = computer.uptime() + end +end + +loginSubmitButton.onTouch = function() + config.server = loginServerInput.text + config.port = tonumber(loginPortInput.text) + config.username = loginUsernameInput.text + config.password = loginPasswordInput.text + saveConfig() + + status("Connecting to server socket...") + + local result, reason = component.internet.connect(config.server, config.port) + if result then + socketHandle = result + repeat + os.sleep(socketConnectDelay) + until socketHandle.finishConnect() + + status("Connection estabilished, waiting for response...") + socketWrite("") + else + GUI.alert("Failed to connect to server: " .. tostring(socketHandle)) + end +end + +contactAddButton.onTouch = function() + backgroundContainer.hidden = false + + backgroundLayout:removeChildren() + backgroundLayout:addChild(addContactInput) + backgroundLayout:addChild(addContactSubmitButton) + + mainContainer:drawOnScreen() +end + +addContactSubmitButton.onTouch = function() + backgroundContainer.hidden = true + local text = addContactInput.text + addContactInput.text = "" + + if #text > 0 and not history[text] then + selectItem(addContactItemToList(text)) + else + mainContainer:drawOnScreen() + end +end + +contactRemoveButton.onTouch = function() + history[selectedItem.text] = nil + selectedItem:remove() + + if selectedItem.joined then + socketWrite("PART " .. selectedItem.text) + end + + selectItem(systemList:getItem(socketUsername)) + updateLeftLayout() + + mainContainer:drawOnScreen() + saveConfig() +end + +loginPasswordSwitchAndLabel.switch.onStateChanged = function() + status() +end + +window.onResize = function(width, height) + backgroundContainer.width, backgroundContainer.height = width, height + backgroundPanel.width, backgroundPanel.height = backgroundContainer.width, backgroundContainer.height + backgroundLayout.width, backgroundLayout.height = backgroundContainer.width, backgroundContainer.height + chat.localX, chat.width, chat.height = leftLayout.width + 1, width - leftLayout.width - window.backgroundPanel.width, height - chatInput.height + window.backgroundPanel.localX, window.backgroundPanel.height = width - window.backgroundPanel.width + 1, height + rightLayout.localX, rightLayout.height = window.backgroundPanel.localX + 1, window.backgroundPanel.height + leftPanel.height = height - contactLayout.height + leftLayout.height = leftPanel.height + contactLayout.localY = height - contactLayout.height + 1 + scrollBar.localX, scrollBar.height = chat.localX + chat.width - 1, chat.height + + chatInputLayout.localX, chatInputLayout.localY, chatInputLayout.width = chat.localX, height - chatInputLayout.height + 1, chat.width + chatInput.width = chatInputLayout.width - chatComboBox.width +end + +scrollBar.onTouch = function() + scrollBar.value = math.round(scrollBar.value) + if scrollBar.value < 1 then + scrollBar.value = 1 + elseif scrollBar.value > #history[selectedItem.text] then + scrollBar.value = #history[selectedItem.text] + end + + mainContainer:drawOnScreen() +end + +local overrideWindowClose = window.close +window.close = function(...) + if socketHandle then + socketWrite("QUIT") + end + overrideWindowClose(...) + + -- for key in pairs(history) do + -- history[key] = {} + -- end + table.toFile(historyPath, history) +end + +------------------------------------------------------------------------------- + +for key in pairs(history) do + addContactItemToList(key) +end + +window:resize(window.width, window.height) + +selectItem(systemList:getItem(socketUsername)) +login() +-- backgroundContainer.hidden = true \ No newline at end of file