Skip to content

Instantly share code, notes, and snippets.

@ZeekDaGeek
Last active December 14, 2020 21:11
Show Gist options
  • Save ZeekDaGeek/1177c00e88695e2cd4395c25124d66c4 to your computer and use it in GitHub Desktop.
Save ZeekDaGeek/1177c00e88695e2cd4395c25124d66c4 to your computer and use it in GitHub Desktop.
development_sync
{"description":"development_sync","updated_at":"2020-12-14T21:11:54.457Z","files":["auto_mini.lua","autodownload.lua","bridge.lua","drone_bios.lua","drone_client.lua","geeklib.lua","gist_sync.lua","robot_controller.lua","robot_idle.lua","stair_down.lua","tree_farm.lua"]}
local component = require("component")
local sides = require("sides")
local event = require("event")
---- Config
local miniSize = 3
local AutoclickerName = "clickmachine:auto_clicker"
local AutoclickerAltName = "thermalexpansion:device"
local ChestName = "thermalexpansion:strongbox"
local DropperName = "minecraft:dropper"
---- Session Variables
local Autoclickers = {}
local Dropper = {}
---- Functions
function SetupTransposer(componentAddress)
local transposer = component.proxy(componentAddress)
local machineType = nil
local machineSide = nil
local chestSide = nil
--- Find the inventories.
for i = 0, 5, 1 do
local currentInv = transposer.getInventoryName(i)
--print(currentInv)
if (currentInv == AutoclickerName) then
machineType = 1
machineSide = i
elseif (currentInv == AutoclickerAltName) then
machineType = 1
machineSide = i
elseif (currentInv == DropperName) then
machineType = 2
machineSide = i
elseif (currentInv == ChestName) then
chestSide = i
end
end
if (machineType ~= nil and machineSide ~= nil and chestSide ~= nil) then
-- Transposer is attatched to an autoclicker
if (machineType == 1) then
SetupAutoclicker(componentAddress, machineSide, chestSide)
elseif (machineType == 2) then
SetupDropper(componentAddress, machineSide, chestSide)
end
end
end
function SetupAutoclicker(transAddress, autoclickerSide, chestSide)
output = { address = transAddress, side = autoclickerSide, chest = chestSide }
table.insert(Autoclickers, output)
--print("Table is now:")
--for key, value in pairs(Autoclickers) do
-- print(" " .. key .. ": " .. value.side .. ", " .. value.chest)
--end
end
function SetupDropper(transAddress, dropperSide, chestSide)
Dropper = {}
Dropper = { address = transAddress, side = dropperSide, chest = chestSide }
end
function init()
-- Zero out states.
Autoclickers = {}
Dropper = {}
-- Look at all of the transposers and figure out which machine type they're
-- attached to.
componentList = component.list("transposer")
for address, componentType in component.list("transposer") do
--print("Setting up transposer " .. address)
SetupTransposer(address)
-- Debug for finding inventory names.
--[[
if (not string.find(address, "52748")) then goto continue end
--SetupTransposer(address)
print(address)
print(" up: " .. component.proxy(address).getInventoryName(sides.east))
print(" down: " .. component.proxy(address).getInventoryName(sides.down))
::continue::
]]--
end
end
function Craft()
for _, transposerInfo in pairs(Autoclickers) do
transposer = component.proxy(transposerInfo.address)
local height = 1
for height = 1, miniSize, 1 do
local currentSlot = height
local foundSlot = false
local result = transposer.transferItem(transposerInfo.chest, transposerInfo.side, 1, currentSlot, 1)
os.sleep(0.2)
--print("Attempt to move item: " .. result)
--while (foundSlot == false) do
-- transposer.getSlotStackSize(transposerInfo.chest, currentSlot)
--end
end
end
dropTrans = component.proxy(Dropper.address)
dropTrans.transferItem(Dropper.chest, Dropper.side, 1, 1)
rs = component.getPrimary("redstone")
rs.setOutput(sides.south, 15)
os.sleep(0.2)
rs.setOutput(sides.south, 0)
end
---- Main
init()
print("Place autoclicker and press a key...")
event.pull("key_down")
Craft()
local component = require("component")
local term = require("term")
local shell = require("shell")
local fs = require("filesystem")
if not component.isAvailable("internet") then
term.write("Internet Card is not available. Cannot download the script.\n")
return false
end
local args, options = shell.parse(...)
local url = tostring(args[1])
local filename = fs.name(url)
term.write("Creating an auto-downloading version of " .. filename .. "\n")
if (not fs.isDirectory("/home/.autodownloader/cache")) then
fs.makeDirectory("/home/.autodownloader")
fs.makeDirectory("/home/.autodownloader/cache")
end
local cache_filename = "/home/.autodownloader/cache/" .. string.gsub(shell.getWorkingDirectory(), "/", ".") .. "." .. filename
local fh = fs.open(shell.getWorkingDirectory() .. "/" .. filename, "w")
fh:write(string.format([[
local i = require("internet")
local fs = require("filesystem")
local buffer = ""
local ih = i.request("%s?" .. os.time(), {}, {})
for chunk in ih do buffer = buffer..chunk end
local filepath = "%s"
local fh = fs.open(filepath, "w")
fh:write(buffer)
local args = table.concat({...}, " ")
os.execute(filepath .. " " .. args)
]], url, cache_filename))
term.write("Run " .. filename .. " to automatically update and run the program each time.\n\n")
local component = require("component")
local robot = require("robot")
local sides = require("sides")
local shell = require("shell")
local InvSize = robot.inventorySize()
local args, options = shell.parse(...)
local BridgeAmount = tonumber(args[1])
-----
function PlaceLike(slot, ...)
local args = table.pack(...)
local side = args[1] or sides.forward
if (robot.compareTo(slot) and (robot.count() > 1 or robot.select() ~= slot)) then
component.robot.place(side)
return true
end
robot.select(slot)
for i=1,InvSize do
if (robot.compareTo(i)) then
if (i ~= slot or robot.count(i) > 1) then
robot.select(i)
component.robot.place(side)
return true
end
end
end
print("Err: out of blocks")
os.exit()
end
-----
print("Attempting to bridge " .. tostring(BridgeAmount) .. " blocks.")
for i=1,BridgeAmount,1 do
print(" Placing block #" .. tostring(i) .. ".")
robot.forward()
result = PlaceLike(1, sides.down)
end
local m=component.proxy(component.list("modem")())
m.open(2412)
local function respond(...)
local args=table.pack(...)
pcall(function() m.broadcast(2412, table.unpack(args)) end)
end
local function receive()
while true do
local evt,_,_,_,_,cmd=computer.pullSignal()
if evt=="modem_message" then return load(cmd) end
end
end
while true do
local result,reason=pcall(function()
local result,reason=receive()
if not result then return respond(reason) end
respond(result())
end)
if not result then respond(reason) end
end
local component = require("component")
local event = require("event")
local modem = component.modem
modem.open(2412)
modem.broadcast(2412, "drone=component.proxy(component.list('drone')())")
while true do
local cmd=io.read()
if not cmd then return end
modem.broadcast(2412, cmd)
print(select(6, event.pull(5, "modem_message")))
end
local robot = require("robot")
local fs = require("filesystem")
local serialization = require("serialization")
local geeklib = {}
local state = {}
----- State Management -----
local state_directory = "/home/.state/"
function state.load(state_name)
if (state_name == '' or state_name == nil) then
error("No state name provided.")
end
local state_file = state_directory + state_name + ".dat"
local file
-- Check if the file exists, if not create it.
if (not(fs.exists(state_file))) then
file = io.open(state_file, "w")
file.write(serialization.serialize({}))
file.close()
end
file = io.open(state_file, "r")
state_object = serialization.unserialize(file.read("*a"))
file.close()
return state_object
end
function state.save(state_name, object)
if (state_name == '' or state_name == nil) then
error("No state name provided.")
end
local state_file = state_directory + state_name + ".dat"
local file
-- Check if the file exists, if not create it.
if (not(fs.exists(state_file))) then
file = io.open(state_file, "w")
file.write(serialization.serialize({}))
file.close()
else
file = io.open(state_file, "w")
file.write(serialization.serialize(object))
file.close()
end
return true
end
function state.set(state_name, variable_name, value)
if (state_name == '' or state_name == nil) then
state_name = "general"
end
-- Check if there is a state file for this program.
local state_object = state.load(state_name)
state_object[variable_name] = value
state.save(state_object)
return state_object
end
function state.get(state_name, variable_name)
if (state_name == '' or state_name == nil) then
state_name = "general"
end
local state_object = state.load(state_name)
return state_object[variable_name]
end
geeklib.state = state
--------------- Common Robot functions ---------------
function geeklib.PlaceLike(slot, ...)
local args = table.pack(...)
local side = args[1] or sides.forward
if (robot.compareTo(slot) and (robot.count() > 1 or robot.select() ~= slot)) then
component.robot.place(side)
return true
end
robot.select(slot)
for i=1,InvSize do
if (robot.compareTo(i)) then
if (i ~= slot or robot.count(i) > 1) then
robot.select(i)
component.robot.place(side)
return true
end
end
end
print("Err: out of blocks")
os.exit()
end
return geeklib
-- Updated text...
local component = require("component")
local filesystem = require("filesystem")
local serialization = require("serialization")
local shell = require("shell")
local colors = require("colors")
local event = require("event")
local keyboard = require("keyboard")
local computer = require("computer")
local term = require("term")
local thread = require("thread")
local gpu = component.gpu
-- Configurations
local sleepDuration = 0.05
local updateDelay = 10
local githubAPIOAuthToken = "457f92b0cd4b616f71bd5c6f8115d82619340631"
local gistSyncID = "1177c00e88695e2cd4395c25124d66c4"
local gistUsername = "ZeekDaGeek"
local githubAPI = "https://api.github.com/"
local gistConentURI = "https://gist.githubusercontent.com/"
-- Internal variables
local updateTicks = updateDelay
local updateRunning = false
local localFiles = {}
local allFiles = {}
local defaultGpuDepth = 0
local lastCycleTime = computer.uptime()
local gistInfo = {}
-- Check that there is an internet card.
if (not component.isAvailable("internet")) then
print("EXITING: Program Sync requires Internet Card to work.")
os.exit()
end
-- Check that HTTP requests are enabled in the config.
if (not component.internet.isHttpEnabled) then
print("EXITING: Program Sync requires HTTP requests enabled (config setting).")
os.exit()
end
local internet = require("internet")
local internetComp = component.internet
-- Load persistant program information.
if (not filesystem.exists("/etc/program_sync.dat")) then
if (not filesystem.exists("/etc")) then
filesystem.makeDirectory("/etc")
end
local defaultPersist = {}
defaultPersist.lastUpdated = false
defaultPersist.gistFiles = {}
defaultPersist.pointerIndex = 0
defaultPersist.lastUpdatedOS = 0
local file = io.open("/etc/program_sync.dat", "w")
file:write(serialization.serialize(defaultPersist))
file:close()
end
local file = io.open("/etc/program_sync.dat", "r")
local persist = serialization.unserialize(file:read("*all"))
file:close()
-- Download the JSON API if it hasn't been downloaded yet
if (not filesystem.exists("/usr/lib/json.lua")) then
if (not filesystem.exists("/usr/lib")) then
filesystem.makeDirectory("/usr/lib")
end
local file = io.open("/usr/lib/json.lua", "w")
for chunk in internet.request("http://regex.info/code/JSON.lua") do
file:write(chunk)
end
file:close()
end
local JSON = loadfile("/usr/lib/json.lua")()
-- Internal functions
function savePersist ()
if (not filesystem.exists("/etc")) then
filesystem.makeDirectory("/etc")
end
local file = io.open("/etc/program_sync.dat", "w")
file:write(serialization.serialize(persist))
file:close()
end
function addTrailingSlash (uri)
if string.sub(uri, -1) == "/" then
return uri
else
return uri .. "/"
end
end
function gistAPI (callURI, args)
local gistURI = ""
-- Ensure that the Github API URI has a slash at the end.
gistURI = addTrailingSlash(githubAPI)
-- Check if the call URI has a slash at the beginning, if it does remove it.
if (string.sub(callURI, 1, 1) == "/") then
gistURI = gistURI .. addTrailingSlash(string.sub(callURI, 2))
else
gistURI = gistURI .. addTrailingSlash(callURI)
end
-- Add all arguments to the end of the URI.
if (type(args) == "string") then
gistURI = gistURI .. addTrailingSlash(args)
elseif (type(args) == "table") then
for k, v in pairs(args) do
gistURI = gistURI .. addTrailingSlash(v)
end
end
-- Remove the trailing slash.
gistURI = string.sub(gistURI, 1, string.len(gistURI) - 1)
if (githubAPIOAuthToken) then
gistURI = gistURI .. "?access_token=" .. githubAPIOAuthToken
end
-- print("Requesting URI: " .. gistURI) -- Debug url display.
-- First check if there is an Index in the gist, if there is it'll be a lot faster to use that instead.
-- Build Gist Content URL
gistContentURI = addTrailingSlash(gistConentURI) .. addTrailingSlash(gistUsername) .. addTrailingSlash(gistSyncID) .. "raw/"
local gistResponse = ""
local result, response = pcall(internet.request, gistContentURI .. ".index.json")
if result then
for chunk in response do
gistResponse = gistResponse .. chunk
end
--[[
The format of index.json is expected to be the same.
{
"description": "Any description"
"updated_at": "<Any format as long as it increases with each update>",
"files": {
"filesnames.lua",
"that_exist.lua",
"in_this_gist.lua"
}
}
]]--
local data = JSON:decode(gistResponse)
local files = {}
for _, filename in pairs(data.files) do
local result, response = pcall(internet.request, gistContentURI .. filename)
if result then
local fileContents = ""
for chunk in response do
fileContents = fileContents .. chunk
end
files[filename] = {
["content"] = fileContents
}
end
end
data["files"] = files
return data
else
-- If we can't find the file return the API response instead.
for chunk in internet.request(gistURI, nil, { ["User-Agent"] = "OpenComputers Gist Sync" }) do gistResponse = gistResponse .. chunk end
local data = JSON:decode(gistResponse)
return data
end
end
function updateLocalFiles ()
localFiles = {}
for file in filesystem.list(shell.getWorkingDirectory()) do
table.insert(localFiles, file)
end
localFiles = localFiles
return localFiles
end
function updateFiles ()
local fileList = {}
local listSize = 0
for _, file in pairs(localFiles) do
fileList[file] = {}
fileList[file].existsLocally = true
fileList[file].existsOnGist = false
listSize = listSize + 1
end
for _, file in pairs(persist.gistFiles) do
if (not fileList[file]) then
fileList[file] = {}
fileList[file].existsLocally = false
listSize = listSize + 1
end
fileList[file].existsOnGist = true
end
allFiles = {}
allFiles = fileList
allFiles["_metadata"] = { size = listSize }
if (persist.pointerIndex > allFiles["_metadata"].size) then
persist.pointerIndex = allFiles["_metadata"].size
savePersist()
end
--print("localFiles: " .. serialization.serialize(localFiles))
--print("gistFiles: " .. serialization.serialize(persist.gistFiles))
--print("fileList: " .. serialization.serialize(fileList))
--print("allFiles: " .. serialization.serialize(allFiles))
return sortedFileList
end
function setForeground(newColor, pallete)
if gpu.getDepth() == 4 then
gpu.setForeground(newColor, pallete)
end
end
function setBackground(newColor, pallete)
if gpu.getDepth() == 4 then
gpu.setBackground(newColor, pallete)
end
end
function drawUI()
local w, h = gpu.getResolution()
-- Clear the screen
setBackground(colors.black, true)
gpu.fill(1, 1, w, h, " ")
-- Draw borders
setBackground(colors.silver, true)
gpu.fill(2, 2, w - 3, 1, " ")
gpu.fill(2, 3, 1, h - 3, " ")
gpu.fill(w - 2, 2, 1, h - 2, " ")
gpu.fill(3, h - 1, w - 4, 1, " ")
setForeground(colors.black, true)
updatedDelta = os.time() - persist.lastUpdatedOS
if updatedDelta < 0 then
if persist.lastUpdatedOS > -1 then
persist.lastUpdatedOS = -1
savePersist()
end
updatedString = ""
else
updatedString = string.format("(Updated %.0fs ago)", math.floor(updatedDelta / 100))
end
local loadingStates = {}
loadingStates[0] = "..."
loadingStates[1] = " .."
loadingStates[2] = " ."
loadingStates[3] = ". "
loadingStates[4] = ".. "
local updateIcon = loadingStates[math.floor(math.fmod(computer.uptime(), 5))]
-- Shorten title on smaller screens.
local programTitle = "Gist Program Sync"
if (w <= 60) then
programTitle = "GPSync"
end
gpu.set(3, 2, string.format("%s - %s %s %s", programTitle, gistInfo.desc or "", updatedString, updateIcon))
local tmpString = "Keybinds: E(x)it (H)elp"
gpu.set(w - string.len(tmpString) - 2, h - 1, tmpString)
setForeground(colors.white, true)
setBackground(colors.black, true)
-- Help text.
helpPara = {
"Developing OpenComputer programs on a server is a pain in the butt, the " ..
"text console is not designed well for lag less text input and it gets " ..
"very annoying very quickly to develop. That's where Gist Program Sync " ..
"becomes useful.",
"Simply run Gist Program Sync from a directory you would like to " ..
"sync with a gist and it will automatically download updates when it finds them.",
"Run or download a program from Gist by simply pressing the corrisponding" ..
"number key or select the file with your arrow keys and hit enter.",
"Filename color key:"
}
local timeTillUpdate = updateDelay - updateTicks
if (timeTillUpdate <= 0) then
gpu.set(4, h-3, "Checking Gist...")
else
gpu.set(4, h-3, string.format("Checking Gist for updates in %.2f seconds.", timeTillUpdate))
end
-- Combine the local file list with the gist file list.
updateFiles()
-- Sort the file list.
local sortedFileNames = {}
for file, _ in pairs(allFiles) do
if (file ~= "_metadata") then table.insert(sortedFileNames, file) end
end
table.sort(sortedFileNames)
-- Print out the list of files.
local listMax = h - 4 - 5
local listMin = persist.pointerIndex - listMax + 3
local currentLine, keybind, i = 4, 1, 0
for _, fileName in pairs(sortedFileNames) do
if (fileName ~= "_metadata" and i >= listMin and (currentLine - 4) < listMax) then
fileInfo = allFiles[fileName]
if fileInfo.existsLocally and fileInfo.existsOnGist then
setForeground(colors.green, true)
debug = "green"
file_prefix = "[X] "
elseif fileInfo.existsLocally then
setForeground(colors.white, true)
debug = "white"
file_prefix = "[ ] "
else
setForeground(colors.gray, true)
debug = "grey"
file_prefix = "[-] "
end
local tmpPrefix = ""
if (keybind) then
allFiles[fileName].keybind = keybind + 1
if (keybind == 10) then
tmpPrefix = " 0. "
else
tmpPrefix = string.format(" %s. ", keybind)
end
if (keybind < 10) then
keybind = keybind + 1
else
keybind = nil
end
end
local swapForeground, swapForegroundRGB = gpu.getForeground()
local swapBackground, swapBackgroundRGB = gpu.getBackground()
if (persist.pointerIndex == i) then
setForeground(swapBackground, swapBackgroundRGB)
setBackground(swapForeground, swapForegroundRGB)
tmpPrefix = ">" .. tmpPrefix
else
tmpPrefix = " " .. tmpPrefix
end
if (gpu.getDepth() == 1) then
gpu.set(5, currentLine, tmpPrefix .. file_prefix .. fileName .. " ")
else
gpu.set(5, currentLine, tmpPrefix .. fileName .. " ")
end
setForeground(swapForeground, swapForegroundRGB)
setBackground(swapBackground, swapBackgroundRGB)
currentLine = currentLine + 1
end
i = i + 1
end
setForeground(colors.white, true)
local currentX, currentY = 2, 2
gpu.set(2, 2, "")
term.setCursor(1, h)
end
-- Initialize.
defaultGpuDepth = gpu.getDepth()
if gpu.maxDepth() > 4 then
gpu.setDepth(4)
end
local currentlyUpdating = false
local updateThread = thread.create(function()
while true do
timeDiff = computer.uptime() - lastCycleTime
updateTicks = updateTicks + timeDiff
lastCycleTime = computer.uptime()
-- Check if we should do another update check.
if (updateTicks >= updateDelay and updateRunning == false) then
updateRunning = true
updateLocalFiles()
gistObject = gistAPI("/gists", { gistSyncID })
gistInfo.desc = gistObject.description
-- Check if the gist has been updated since the last we checked.
if (persist.lastUpdated == false or gistObject.updated_at > persist.lastUpdated) then
persist.lastUpdatedOS = os.time()
persist.gistFiles = {}
for fileName, fileInfo in pairs(gistObject.files) do
table.insert(persist.gistFiles, fileName)
-- Check if the file exists in current directory, if it does then update it.
if (filesystem.exists(shell.getWorkingDirectory() .. "/" .. fileName)) then
local w,h = gpu.getResolution()
gpu.set(4, h-3, string.format("Updating %s/%s...", shell.getWorkingDirectory(), fileName))
-- Rewrite the file.
local file = filesystem.open(shell.getWorkingDirectory() .. "/" .. fileName, "w")
file:write(fileInfo.content)
file:close()
end
end
persist.lastUpdated = gistObject.updated_at
savePersist()
end
updateRunning = false
lastCycleTime = computer.uptime()
updateTicks = 0
end
os.sleep(sleepDuration)
end
end)
while true do
drawUI()
local eventId, keyboardId, char, code, player = event.pull(sleepDuration)
if (eventId == "key_down") then
if (code == keyboard.keys.f) then
persist.lastUpdated = false
lastCycleTime = 0
savePersist()
drawUI()
elseif (code == keyboard.keys.x) then
local w, h = gpu.getResolution()
setBackground(colors.black, true)
setForeground(colors.white, true)
gpu.fill(1,1, w, h, " ")
gpu.setDepth(defaultGpuDepth)
term.setCursor(0, 0)
print("")
print("Exiting Gist program sync.")
os.exit()
elseif (code == keyboard.keys.up) then
persist.pointerIndex = persist.pointerIndex - 1
if (persist.pointerIndex < 0) then
persist.pointerIndex = 0
elseif (persist.pointerIndex > (allFiles["_metadata"].size - 1)) then
persist.pointerIndex = allFiles["_metadata"].size - 1
end
savePersist()
drawUI()
elseif (code == keyboard.keys.down) then
persist.pointerIndex = persist.pointerIndex + 1
if (persist.pointerIndex < 0) then
persist.pointerIndex = 0
elseif (persist.pointerIndex > (allFiles["_metadata"].size - 1)) then
persist.pointerIndex = allFiles["_metadata"].size - 1
end
savePersist()
drawUI()
-- Detect if a number hotkey is pressed.
elseif code >= 2 and code <= 11 then
for fileName, file in pairs(allFiles) do
if (file.keybind == code) then
if (file.existsLocally) then
print("")
print(string.format("Launching %s...", fileName))
os.execute(shell.getWorkingDirectory() .. "/" .. fileName)
print("Press any key to continue Gist Program Sync...")
event.pull(100, "key_down")
elseif (not file.existsLocally and file.existsOnGist) then
print("")
print(string.format("Do you wish to download %s from Gist next cycle? [Y/n]", fileName))
updateThread:suspend()
local userInput = io.read()
updateThread:resume()
if (userInput == "" or userInput == "y" or userInput == "yes") then
-- Rewrite the file.
local file = io.open(shell.getWorkingDirectory() .. "/" .. fileName, "w")
file:write()
file:close()
persist.lastUpdated = 0
savePersist()
updateTicks = updateDelay
end
end
end
end
elseif (code == keyboard.keys.enter) then
local sortedFileNames = {}
for file, _ in pairs(allFiles) do
if (file ~= "_metadata") then table.insert(sortedFileNames, file) end
end
table.sort(sortedFileNames)
local i = 0
for _, fileName in pairs(sortedFileNames) do
if (i == persist.pointerIndex) then
file = allFiles[fileName]
if (file.existsLocally) then
print("")
print("Launching " .. fileName .. "...")
os.execute(shell.getWorkingDirectory() .. "/" .. fileName)
print("Press any key to continue Gist Program Sync...")
event.pull(100, "key_down")
elseif (not file.existsLocally and file.existsOnGist) then
--[[
print("")
print(string.format("Do you wish to download %s from Gist next cycle? [Y/n]", fileName))
updateThread:suspend()
os.sleep(5)
local userInput = term.read()
updateThread:resume()
]]--
--if (userInput == "" or userInput:lower() == "y" or userInput:lower() == "yes") then
-- Rewrite the file.
local file = io.open(shell.getWorkingDirectory() .. "/" .. fileName, "w")
file:write()
file:close()
persist.lastUpdated = false
savePersist()
updateTicks = updateDelay
--end
end
end
i = i + 1
end
end
end
end
-- A (very (very!)) simple IRC client. Reference:
-- http://tools.ietf.org/html/rfc2812
local component = require("component")
local computer = require("computer")
if not component.isAvailable("internet") then
io.stderr:write("This requires an Internet Card to run!\n")
return
end
if not component.isAvailable("modem") then
io.stderr:write("This requires a Modem to run!\n")
return
end
local event = require("event")
local internet = require("internet")
local term = require("term")
local text = require("text")
local serial = require("serialization")
local modem = component.modem
local modemPort = 1069
modem.open(modemPort)
local nick = "justinfan6969"
local host = "irc.chat.twitch.tv"
local default_channel = "#zeekdageek"
if not host:find(":") then
host = host .. ":6667"
end
-- try to connect to server.
local sock, reason = internet.open(host)
if not sock then
io.stderr:write(reason .. "\n")
return
end
-- custom print that uses all except the last line for printing.
local function print(message, overwrite)
local w, h = component.gpu.getResolution()
local line
repeat
line, message = text.wrap(text.trim(message), w, w)
if not overwrite then
component.gpu.copy(1, 1, w, h - 1, 0, -1)
end
overwrite = false
component.gpu.fill(1, h - 1, w, 1, " ")
component.gpu.set(1, h - 1, line)
until not message or message == ""
end
-- utility method for reply tracking tables.
function autocreate(table, key)
table[key] = {}
return table[key]
end
-- extract nickname from identity.
local function name(identity)
return identity and identity:match("^[^!]+") or identity or "Anonymous"
end
-- user defined callback for messages (via `lua function(msg) ... end`)
local callback = nil
-- timer used to drive socket reading.
local timer
-- main command handling callback.
local function handleCommand(prefix, command, args, message)
---------------------------------------------------
-- Keepalive
if command == "PING" then
sock:write(string.format("PONG :%s\r\n", message))
sock:flush()
---------------------------------------------------
-- General commands
elseif command == "PRIVMSG" then
local ctcp = message:match("^\1(.-)\1$")
if ctcp then
local orig_ctcp, param = ctcp:match("^(%S+) ?(.-)$")
ctcp = orig_ctcp:upper()
if ctcp == "PING" then
sock:write("NOTICE " .. name(prefix) .. " :\001PING " .. param .. "\001\r\n")
sock:flush()
elseif ctcp == "ACTION" then
print("[" .. args[1] .. "] * " .. name(prefix) .. string.gsub(string.gsub(message, "\001ACTION", ""), "\001", ""))
else
-- Here we print the CTCP message if it was unhandled...
print("[" .. name(prefix) .. "] CTCP " .. orig_ctcp)
end
else
if string.find(message, nick) then
computer.beep()
end
print("[" .. args[1] .. "] " .. name(prefix) .. ": " .. message)
end
elseif command == "NOTICE" then
print("[NOTICE] " .. message)
elseif command == "ERROR" then
print("[ERROR] " .. message)
-- MOTD end received, we can now join channels.
elseif command == "376" then -- ignore
sock:write("join " .. default_channel .. "\r\n")
sock:write("CAP REQ :twitch.tv/membership\r\n")
sock:write("CAP REQ :twitch.tv/tags\r\n")
sock:flush()
---------------------------------------------------
-- Unhandled message.
end
end
-- catch errors to allow manual closing of socket and removal of timer.
local result, reason = pcall(function()
-- say hello.
term.clear()
--print("Welcome to OpenIRC!")
-- avoid sock:read locking up the computer.
sock:setTimeout(0.05)
-- http://tools.ietf.org/html/rfc2812#section-3.1
sock:write(string.format("NICK %s\r\n", nick))
sock:write(string.format("USER %s 0 * :%s [OpenComputers]\r\n", nick:lower(), nick))
sock:flush()
-- socket reading logic (receive messages) driven by a timer.
timer = event.timer(0.5, function()
if not sock then
return false
end
repeat
local ok, line = pcall(sock.read, sock, 'l*')
if ok then
if not line then
print("Connection lost.")
sock:close()
sock = nil
return false
end
line = text.trim(line) -- get rid of trailing \r
-- #here
print("[RAW]" .. line)
local displaycolor = line:match("color=#([^;]+);");
local displayname = line:match("display%-name=([^;]+);")
local subscriber = line:match("subscriber=(%d);")
local output = line:match("@.- :.- PRIVMSG #.- :(.+)")
local match, prefix = line:match("^(:(%S+) )")
if match then line = line:sub(#match + 1) end
local match, command = line:match("^(([^:]%S*))")
if match then line = line:sub(#match + 1) end
local args = {}
repeat
local match, arg = line:match("^( ([^:]%S*))")
if match then
line = line:sub(#match + 1)
table.insert(args, arg)
end
until not match
local message = line:match("^ :(.*)$")
if subscriber == "1" then
modem.broadcast(modemPort, displayname, displaycolor, output)
print("!! @" .. modemPort .. ":" .. displayname .. displaycolor .. output)
end
handleCommand(prefix, command, args, message)
end
until not ok
end, math.huge)
-- default target for messages, so we don't have to type /msg all the time.
local target = nil
-- command history.
local history = {}
repeat
local w, h = component.gpu.getResolution()
term.setCursor(1, h)
term.write((target or "?") .. "> ")
local line = term.read(history)
if sock and line and line ~= "" then
line = text.trim(line)
if line:lower():sub(1,4) == "/me " then
print("[" .. (target or "?") .. "] " .. nick .. " " .. line:sub(5), true)
elseif line~="" then
print("[" .. (target or "?") .. "] " .. nick .. ": " .. line, true)
end
if line:lower():sub(1, 5) == "/msg " then
local user, message = line:sub(6):match("^(%S+) (.+)$")
if message then
message = text.trim(message)
end
if not user or not message or message == "" then
print("Invalid use of /msg. Usage: /msg nick|channel message.")
line = ""
else
target = user
line = "PRIVMSG " .. target .. " :" .. message
end
elseif line:lower():sub(1, 6) == "/join " then
local channel = text.trim(line:sub(7))
if not channel or channel == "" then
print("Invalid use of /join. Usage: /join channel.")
line = ""
else
target = channel
line = "JOIN " .. channel
end
elseif line:lower():sub(1, 5) == "/lua " then
local script = text.trim(line:sub(6))
local result, reason = load(script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
if not result then
result, reason = load("return " .. script, "=stdin", nil, setmetatable({print=print, socket=sock, nick=nick}, {__index=_G}))
end
line = ""
if not result then
print("Error: " .. tostring(reason))
else
result, reason = pcall(result)
if not result then
print("Error: " .. tostring(reason))
elseif type(reason) == "function" then
callback = reason
elseif reason then
line = tostring(reason)
end
end
elseif line:lower():sub(1,4) == "/me " then
if not target then
print("No default target set. Use /msg or /join to set one.")
line = ""
else
line = "PRIVMSG " .. target .. " :\001ACTION " .. line:sub(5) .. "\001"
end
elseif line:sub(1, 1) == "/" then
line = line:sub(2)
elseif line ~= "" then
if not target then
print("No default target set. Use /msg or /join to set one.")
line = ""
else
line = "PRIVMSG " .. target .. " :" .. line
end
end
if line and line ~= "" then
sock:write(line .. "\r\n")
sock:flush()
end
end
until not sock or not line
end)
if sock then
sock:write("QUIT\r\n")
sock:close()
end
if timer then
event.cancel(timer)
end
if not result then
error(reason, 0)
end
return reason
local component = require("component")
local event = require("event")
local serial = require("serialization")
local robot = component.robot
if not component.isAvailable("modem") then
io.stderr:write("Robot idle actions require a modem!\n")
return
end
local modem = component.modem
local modemPort = 1069
modem.open(modemPort)
local beep = component.beep
repeat
local _, _, _, port, distance, robotName, color, message = event.pull(30, "modem_message")
if port == modemPort then
print("Received (" .. port .. "): " .. message)
messageObj = serial.unserialize(message)
if robotName == robot.name() then
local newcolor = tonumber("0x" .. color)
if robot.getLightColor ~= newcolor then
robot.setLightColor(newcolor)
end
for beepStr in string.gmatch(message, "[^%s]+") do
length = string.len(beepStr) / 2 * 0.1
beep.beep({[400]=length})
os.sleep(length)
if string.find("!?.,", string.sub(beepStr, -1)) then
os.sleep(0.1)
end
os.sleep(0.1)
end
end
end
until not true
local component = require("component")
local robot = require("robot")
local sides = require("sides")
local shell = require("shell")
local InvSize = robot.inventorySize()
local args, options = shell.parse(...)
local BridgeAmount = tonumber(args[1])
-----
function PlaceLike(slot, ...)
local args = table.pack(...)
local side = args[1] or sides.forward
if (robot.compareTo(slot) and (robot.count() > 1 or robot.select() ~= slot)) then
component.robot.place(side)
return true
end
robot.select(slot)
for i=1,InvSize do
if (robot.compareTo(i)) then
if (i ~= slot or robot.count(i) > 1) then
robot.select(i)
component.robot.place(side)
return true
end
end
end
print("Err: out of blocks")
os.exit()
end
-----
print("Attempting to stair down " .. tostring(BridgeAmount) .. " blocks.")
for i=1,BridgeAmount,1 do
print(" Placing block #" .. tostring(i) .. ".")
robot.forward()
robot.down()
result = PlaceLike(1, sides.down)
end
local component = require("component")
local robot = require("robot")
local geek = require("geeklib")
local sides = require("sides")
local thread = require("thread")
local event = require("event")
local ic = component.inventory_controller
-- Configurations
local TreeSize = 2 -- 1 for oak, 2 for jungle type
local BonemealSide = sides.top
local SaplingSlot = 1
local BonemealSlot = 2
local LogSlot = 3 -- Used for comparisons.
-- Functions.
function PlaceSaplings(size)
-- Default to 1 if not specified.
if (size == nil) then
size = 1
end
if (size == 2) then
-- Select the sapling slot.
if (robot.count(SaplingSlot) > 4) then
robot.select(SaplingSlot)
robot.forward()
robot.forward()
robot.turnRight()
robot.place()
robot.turnLeft()
robot.back()
robot.place()
robot.turnRight()
robot.place()
robot.turnLeft()
robot.back()
robot.place()
return true
else
error("Not enough saplings for a 2x2 tree.")
end
elseif (size == 1) then
if (robot.count(SaplingSlot) > 1) then
robot.select(SaplingSlot)
robot.place()
else
error("Not enough saplings for a 1x1 tree.")
end
return true
end
end
function RefillBonemeal(direction)
-- Default to refilling bonemeal from the front face.
if (direction == nil) then
direction = sides.front
end
if (direction ~= sides.front and direction ~= sides.top and direction ~= sides.bottom) then
error("Invalid side to refill bonemeal from.")
end
robot.select(BonemealSlot)
if (robot.space() == 0) then
return true
end
local CurrentBonemeal = robot.count()
local SuckResult = false
if (direction == sides.front) then
SuckResult = robot.suck(robot.space())
elseif (direction == sides.top) then
SuckResult = robot.suckUp(robot.space())
elseif (direction == sides.bottom) then
SuckResult = robot.suckDown(robot.space())
end
return SuckResult
end
function UseBonemeal(direction)
-- Constantly attempt to bonemeal until you can't anymore.
-- Default side front.
if (direction == nil) then
direction = sides.front
end
if (direction ~= sides.front and direction ~= sides.top and direction ~= sides.bottom) then
error("Invalid side to bonemeal from.")
end
robot.select(BonemealSlot)
local PlaceAttempt = false
while (not(PlaceAttempt)) do
if (direction == sides.front) then
PlaceAttempt = robot.place()
elseif (direction == sides.top) then
PlaceAttempt = robot.placeUp()
elseif (direction == sides.bottom) then
PlaceAttempt = robot.placeDown()
end
end
end
-- Initialize
-- Main loop
local MainThread = thread.create(function()
print("Waiting...")
os.sleep(1000)
print("Done waiting.")
while (true) do
PlaceSaplings(TreeSize)
UseBonemeal(sides.front)
RefillBonemeal(BonemealSide)
end
end)
-- Close thread.
local InteruptTread = thread.create(function()
event.listen("key_down", function(event, address, char, keycode, player)
if (char == "x") then
MainThread:kill()
InteruptTread:kill()
end
end)
end)
thread.waitForAll({MainThread, InteruptTread})
print("Exiting gracefully.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment