Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- JokeBot Turtle Script v1.2
- Tells jokes from JokeAPI and can tell them at an interval.
- Changes:
- - (v1.0) Initial release.
- - (v1.1) Removed joke announcement prefixes.
- Changed joke text color to GOLD.
- Added response to "@all" chat command.
- - (v1.2) Added random API endpoint selection from a list.
- Added support for two-part jokes with different colors for setup (Aqua) and delivery (Gold).
- Ensured API URLs request single or twopart jokes.
- ]]
- --#region Configuration
- local CHAT_BOX_PERIPHERAL_NAME = "chatBox" -- Set to your chat box peripheral name if different
- local COMMAND_PREFIX = "@joke"
- local CHAT_BOT_NAME = "JokeBot"
- local CHAT_BOT_BRACKETS = "[]"
- local CHAT_BOT_BRACKET_COLOR = "&e" -- Yellow color for the brackets
- -- List of API URLs to randomly choose from
- local JOKE_API_URLS = {
- "https://v2.jokeapi.dev/joke/Programming,Dark?type=single,twopart",
- "https://v2.jokeapi.dev/joke/Any?type=single,twopart",
- "https://v2.jokeapi.dev/joke/Programming,Dark?lang=de&type=single,twopart",
- "https://v2.jokeapi.dev/joke/Any?lang=de&type=single,twopart",
- "https://v2.jokeapi.dev/joke/Miscellaneous,Pun?type=single,twopart" -- Added another option for variety
- }
- local DEFAULT_JOKE_INTERVAL_MINUTES = 0 -- 0 means no automatic jokes
- local DEBUG_MODE = false -- Set to true for detailed logging
- local DEBUG_LOG_FILE = "jokebot.log"
- --#endregion
- --#region Peripherals
- local chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
- --#endregion
- --#region Debug Logger
- local function logDebug(message)
- if not DEBUG_MODE then return end
- local logMessage = string.format("[%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), message)
- local file, err = fs.open(DEBUG_LOG_FILE, "a")
- if file then
- file.write(logMessage)
- file.close()
- else
- print("DEBUG LOG ERROR: Could not open " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
- end
- end
- --#endregion
- --#region State Variables
- logDebug("Script initializing state variables...")
- local jokeIntervalMinutes = DEFAULT_JOKE_INTERVAL_MINUTES
- local jokeIntervalTimerId = nil
- logDebug("State variables initialized. Interval: " .. jokeIntervalMinutes .. " min.")
- --#endregion
- --#region Minecraft JSON Text Component Colors
- local COLORS = {
- BLACK = "black", DARK_BLUE = "dark_blue", DARK_GREEN = "dark_green", DARK_AQUA = "dark_aqua",
- DARK_RED = "dark_red", DARK_PURPLE = "dark_purple", GOLD = "gold", GRAY = "gray",
- DARK_GRAY = "dark_gray", BLUE = "blue", GREEN = "green", AQUA = "aqua", RED = "red",
- LIGHT_PURPLE = "light_purple", YELLOW = "yellow", WHITE = "white", RESET = "reset"
- }
- --#endregion
- --#region Helper Functions
- local function sendFormattedChat(messageComponents, recipientUsername)
- logDebug("Attempting to send formatted chat. Recipient: " .. (recipientUsername or "ALL"))
- if not chatBox then
- local plainText = ""
- for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
- local noChatMsg = "[" .. CHAT_BOT_NAME .. "-NoChatBox" .. (recipientUsername and (" to " .. recipientUsername) or "") .. "] " .. plainText
- print(noChatMsg)
- logDebug("ChatBox not found. Printed to console: " .. noChatMsg)
- return
- end
- local jsonMessage = textutils.serialiseJSON(messageComponents)
- if not jsonMessage then
- local fallbackMsg = "Error: Could not serialize message for formatted sending."
- logDebug("JSON Serialization Error. Fallback: " .. fallbackMsg .. " Data: " .. textutils.serialize(messageComponents, {max_depth=2}))
- if recipientUsername then
- chatBox.sendMessageToPlayer(fallbackMsg, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- chatBox.sendMessage(fallbackMsg, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- return
- end
- local success, err
- if recipientUsername then
- success, err = chatBox.sendFormattedMessageToPlayer(jsonMessage, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- success, err = chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- if not success then
- logDebug("Error sending formatted message: " .. (err or "Unknown error") .. ". Attempting fallback to plain text.")
- local plainText = ""
- for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
- if recipientUsername then
- chatBox.sendMessageToPlayer(plainText, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- else
- chatBox.sendMessage(plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
- end
- else
- logDebug("Formatted message sent successfully.")
- end
- os.sleep(0.3)
- end
- local function announce(messageComponents)
- sendFormattedChat(messageComponents)
- end
- local function announceJokePart(textPart, color)
- local lines = {}
- for line in string.gmatch(textPart, "[^\r\n]+") do
- table.insert(lines, line)
- end
- if #lines == 0 and textPart ~= "" then table.insert(lines, textPart) end
- for i, lineText in ipairs(lines) do
- announce({{text = lineText, color = color}})
- if i < #lines then
- os.sleep(0.7) -- Delay between lines of the same part
- end
- end
- end
- local function fetchAndTellJoke(requestingUsername)
- logDebug("Fetching a joke. Requested by: " .. (requestingUsername or "Timer"))
- if not http then
- logDebug("HTTP API not available, cannot fetch joke.")
- announce({{text = "Cannot fetch joke: HTTP API is not available on this system.", color = COLORS.RED}})
- return
- end
- if #JOKE_API_URLS == 0 then
- logDebug("JOKE_API_URLS list is empty.")
- announce({{text = "No Joke API URLs configured. Cannot fetch joke.", color = COLORS.RED}})
- return
- end
- math.randomseed(os.epoch("utc") + os.clock()*1000) -- Reseed for better randomness
- local selectedApiUrl = JOKE_API_URLS[math.random(1, #JOKE_API_URLS)]
- logDebug("Selected API URL: " .. selectedApiUrl)
- local response, err, handle = http.get(selectedApiUrl)
- if not response then
- logDebug("Failed to fetch joke from API. URL: " .. selectedApiUrl .. " Error: " .. (err or "Unknown HTTP error"))
- announce({{text = "Sorry, I couldn't fetch a joke. Error: ", color = COLORS.RED}, {text = tostring(err or "Is http enabled?"), color = COLORS.YELLOW}})
- return
- end
- local responseBody = response.readAll()
- response.close()
- if handle then pcall(function() handle.close() end) end
- local success, jokeData = pcall(textutils.unserialiseJSON, responseBody)
- if not success or type(jokeData) ~= "table" then
- logDebug("Failed to parse joke JSON. Error: " .. tostring(jokeData or "Invalid JSON") .. ". Body was: " .. tostring(responseBody))
- announce({{text = "Sorry, I received a malformed joke. (Could not parse API response).", color = COLORS.YELLOW}})
- return
- end
- if jokeData.error then
- logDebug("Joke API returned an error. Message: " .. (jokeData.message or "Unknown API error"))
- announce({{text = "The joke API said: \"", color = COLORS.RED}, {text = tostring(jokeData.message or "Something went wrong."), color = COLORS.YELLOW}, {text="\"", color = COLORS.RED}})
- return
- end
- if jokeData.type == "single" and type(jokeData.joke) == "string" then
- logDebug("Telling single-part joke. Lang: " .. (jokeData.lang or "N/A"))
- announceJokePart(jokeData.joke, COLORS.GOLD)
- elseif jokeData.type == "twopart" and type(jokeData.setup) == "string" and type(jokeData.delivery) == "string" then
- logDebug("Telling two-part joke. Lang: " .. (jokeData.lang or "N/A"))
- announceJokePart(jokeData.setup, COLORS.AQUA) -- Setup in Aqua
- os.sleep(1.2) -- Pause for comedic timing before delivery
- announceJokePart(jokeData.delivery, COLORS.GOLD) -- Delivery in Gold (orangey)
- else
- logDebug("Joke API returned an unexpected format or missing fields. Data: " .. textutils.serialize(jokeData))
- announce({{text = "Sorry, the joke I got was in a weird format or incomplete. Try again?", color = COLORS.YELLOW}})
- end
- end
- local function startJokeIntervalTimer()
- if jokeIntervalTimerId then
- os.cancelTimer(jokeIntervalTimerId)
- logDebug("Cancelled existing joke interval timer ID: " .. jokeIntervalTimerId)
- jokeIntervalTimerId = nil
- end
- if jokeIntervalMinutes > 0 then
- jokeIntervalTimerId = os.startTimer(jokeIntervalMinutes * 60)
- logDebug("Joke interval timer started. Duration: " .. jokeIntervalMinutes .. " min. New Timer ID: " .. jokeIntervalTimerId)
- else
- logDebug("Joke interval is 0, timer not started.")
- end
- end
- --#endregion
- --#region Command Handlers
- local commandHandlers = {}
- commandHandlers.help = function(username, _)
- logDebug("Executing command: help, User: " .. username)
- announce({{text = "--- JokeBot Commands (".. COMMAND_PREFIX .. ") ---", color = COLORS.GOLD, bold = true}})
- announce({{text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = " - Shows this help message.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " tell", color = COLORS.AQUA}, {text = " - Tells you a random joke.", color = COLORS.GRAY}})
- announce({{text = COMMAND_PREFIX .. " interval <minutes>", color = COLORS.AQUA}, {text = " - Sets automatic joke interval.", color = COLORS.GRAY}})
- announce({{text = " (Use 0 for minutes to disable automatic jokes)", color = COLORS.DARK_GRAY}})
- end
- commandHandlers.tell = function(username, _)
- logDebug("Executing command: tell, User: " .. username)
- fetchAndTellJoke(username)
- end
- commandHandlers.interval = function(username, args)
- logDebug("Executing command: interval, User: " .. username .. ", Args: " .. textutils.serialize(args))
- if #args < 1 then
- announce({{text = "Usage: ", color = COLORS.YELLOW}, {text = COMMAND_PREFIX .. " interval <minutes>", color = COLORS.AQUA}})
- announce({{text = "Current interval: ", color = COLORS.YELLOW}, {text = jokeIntervalMinutes .. " minutes.", color = COLORS.WHITE}})
- return
- end
- local minutes = tonumber(args[1])
- if not minutes or minutes < 0 or math.floor(minutes) ~= minutes then
- announce({{text = "Invalid number of minutes. Must be a whole number (0 or greater).", color = COLORS.RED}})
- return
- end
- jokeIntervalMinutes = minutes
- if minutes > 0 then
- announce({{text = "Okay, " .. username .. "! I'll tell a joke every ", color = COLORS.GREEN}, {text = minutes .. " minute(s).", color = COLORS.AQUA}})
- else
- announce({{text = "Okay, " .. username .. "! Automatic jokes disabled.", color = COLORS.GREEN}})
- end
- logDebug("Joke interval set to " .. jokeIntervalMinutes .. " minutes by " .. username)
- startJokeIntervalTimer()
- end
- --#endregion
- --#region Main Loop
- local function run()
- term.clear()
- term.setCursorPos(1, 1)
- if DEBUG_MODE then
- local file, err = fs.open(DEBUG_LOG_FILE, "w")
- if file then
- file.write(string.format("[%s] JokeBot Script Initializing - DEBUG MODE ENABLED (v1.2)\n", os.date("%Y-%m-%d %H:%M:%S")))
- file.write("======================================================================\n")
- file.close()
- else
- print("DEBUG LOG ERROR: Could not clear/initialize " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
- end
- end
- logDebug("Script run() started.")
- if not chatBox then
- logDebug("WARNING: Chat Box peripheral ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features will be printed to console only.")
- print("WARNING: Chat Box ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features disabled/printed to console.")
- end
- if not http then
- logDebug("FATAL: HTTP API not available! Joke fetching will not work.")
- print("FATAL: HTTP API is not available on this ComputerCraft system. JokeBot cannot fetch jokes.")
- if chatBox then
- announce({{text = "JokeBot Error: ", color = COLORS.RED, bold=true}, {text="HTTP API is not available. Cannot fetch jokes.", color=COLORS.YELLOW}})
- end
- end
- logDebug("Announcing online status.")
- print("JokeBot script started. Type '" .. COMMAND_PREFIX .. " help' in chat or '@all'.")
- if chatBox then
- announce({{text = CHAT_BOT_NAME .. " online!", color = COLORS.GREEN, bold = true}, {text = " Ready to tell some jokes from various sources.", color = COLORS.GRAY}})
- announce({{text = "Type '", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' or '@all' for commands.", color = COLORS.GRAY}})
- end
- startJokeIntervalTimer()
- while true do
- local eventData = {os.pullEvent()}
- local eventType = eventData[1]
- logDebug("Event received: " .. eventType .. " - Data: " .. textutils.serialize(eventData, {compact = true, max_depth = 2}))
- if eventType == "chat" then
- local eUsername, eMessage, _, eIsHidden = eventData[2], eventData[3], eventData[4], eventData[5]
- if not eIsHidden and eMessage then
- if string.lower(eMessage) == "@all" then
- logDebug("@all command received from " .. eUsername)
- announce({{text = "Use '", color = COLORS.GREEN}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' for my commands.", color = COLORS.GREEN}})
- elseif string.sub(eMessage, 1, #COMMAND_PREFIX) == COMMAND_PREFIX then
- logDebug("Chat command received from " .. eUsername .. ": " .. eMessage)
- local parts = {}
- for part in string.gmatch(eMessage, "[^%s]+") do
- table.insert(parts, part)
- end
- local commandName = ""
- if parts[2] then commandName = string.lower(parts[2]) end
- local cmdArgs = {}
- for i = 3, #parts do table.insert(cmdArgs, parts[i]) end
- logDebug("Parsed command: '" .. commandName .. "', Args: " .. textutils.serialize(cmdArgs))
- if commandHandlers[commandName] then
- local success, err = pcall(commandHandlers[commandName], eUsername, cmdArgs)
- if not success then
- logDebug("Error executing command '" .. commandName .. "': " .. tostring(err))
- announce({{text = "Oops! Something went wrong while processing your command: ", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}})
- end
- elseif commandName ~= "" then
- announce({{text = "Unknown command: '", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW},
- {text = "'. Try '", color = COLORS.RED}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "'.", color = COLORS.RED}})
- end
- end
- end
- elseif eventType == "timer" then
- local timerId = eventData[2]
- logDebug("Timer event received for ID: " .. timerId)
- if timerId == jokeIntervalTimerId then
- logDebug("Joke interval timer triggered.")
- fetchAndTellJoke(nil)
- if jokeIntervalMinutes > 0 then
- startJokeIntervalTimer()
- else
- jokeIntervalTimerId = nil
- logDebug("Joke interval was 0 after joke, timer stopped.")
- end
- else
- logDebug("Unknown timer ID: " .. timerId)
- end
- elseif eventType == "terminate" then
- logDebug("Terminate event received. Shutting down.")
- if jokeIntervalTimerId then
- os.cancelTimer(jokeIntervalTimerId)
- logDebug("Joke interval timer cancelled.")
- end
- if chatBox then
- announce({{text = CHAT_BOT_NAME .. " shutting down...", color = COLORS.YELLOW, bold = true}})
- end
- logDebug("Script terminated.")
- print(CHAT_BOT_NAME .. " terminated.")
- return
- end
- end
- end
- run()
- --#endregion
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement