Advertisement
Myros27

JokeBot

May 17th, 2025 (edited)
476
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.85 KB | None | 0 0
  1. --[[
  2.     JokeBot Turtle Script v1.1
  3.     Tells jokes from JokeAPI and can tell them at an interval.
  4.     Changes:
  5.     - (v1.0) Initial release.
  6.     - (v1.1) Removed joke announcement prefixes.
  7.              Changed joke text color to GOLD.
  8.              Added response to "@all" chat command.
  9. ]]
  10.  
  11. --#region Configuration
  12. local CHAT_BOX_PERIPHERAL_NAME = "chatBox" -- Set to your chat box peripheral name if different
  13.  
  14. local COMMAND_PREFIX = "@joke"
  15. local CHAT_BOT_NAME = "JokeBot"
  16. local CHAT_BOT_BRACKETS = "[]"
  17. local CHAT_BOT_BRACKET_COLOR = "&e" -- Yellow color for the brackets
  18.  
  19. local JOKE_API_URL = "https://v2.jokeapi.dev/joke/Programming,Dark?type=single"
  20.  
  21. local DEFAULT_JOKE_INTERVAL_MINUTES = 0 -- 0 means no automatic jokes
  22.  
  23. local DEBUG_MODE = false -- Set to true for detailed logging
  24. local DEBUG_LOG_FILE = "jokebot.log"
  25. --#endregion
  26.  
  27. --#region Peripherals
  28. local chatBox = peripheral.find(CHAT_BOX_PERIPHERAL_NAME)
  29. --#endregion
  30.  
  31. --#region Debug Logger
  32. local function logDebug(message)
  33.     if not DEBUG_MODE then return end
  34.     local logMessage = string.format("[%s] %s\n", os.date("%Y-%m-%d %H:%M:%S"), message)
  35.     local file, err = fs.open(DEBUG_LOG_FILE, "a")
  36.     if file then
  37.         file.write(logMessage)
  38.         file.close()
  39.     else
  40.         print("DEBUG LOG ERROR: Could not open " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
  41.     end
  42. end
  43. --#endregion
  44.  
  45. --#region State Variables
  46. logDebug("Script initializing state variables...")
  47. local jokeIntervalMinutes = DEFAULT_JOKE_INTERVAL_MINUTES
  48. local jokeIntervalTimerId = nil
  49. logDebug("State variables initialized. Interval: " .. jokeIntervalMinutes .. " min.")
  50. --#endregion
  51.  
  52. --#region Minecraft JSON Text Component Colors
  53. local COLORS = {
  54.     BLACK = "black", DARK_BLUE = "dark_blue", DARK_GREEN = "dark_green", DARK_AQUA = "dark_aqua",
  55.     DARK_RED = "dark_red", DARK_PURPLE = "dark_purple", GOLD = "gold", GRAY = "gray",
  56.     DARK_GRAY = "dark_gray", BLUE = "blue", GREEN = "green", AQUA = "aqua", RED = "red",
  57.     LIGHT_PURPLE = "light_purple", YELLOW = "yellow", WHITE = "white", RESET = "reset"
  58. }
  59. --#endregion
  60.  
  61. --#region Helper Functions
  62. local function sendFormattedChat(messageComponents, recipientUsername)
  63.     logDebug("Attempting to send formatted chat. Recipient: " .. (recipientUsername or "ALL"))
  64.     if not chatBox then
  65.         local plainText = ""
  66.         for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
  67.         local noChatMsg = "[" .. CHAT_BOT_NAME .. "-NoChatBox" .. (recipientUsername and (" to " .. recipientUsername) or "") .. "] " .. plainText
  68.         print(noChatMsg)
  69.         logDebug("ChatBox not found. Printed to console: " .. noChatMsg)
  70.         return
  71.     end
  72.  
  73.     local jsonMessage = textutils.serialiseJSON(messageComponents)
  74.     if not jsonMessage then
  75.         local fallbackMsg = "Error: Could not serialize message for formatted sending."
  76.         logDebug("JSON Serialization Error. Fallback: " .. fallbackMsg .. " Data: " .. textutils.serialize(messageComponents, {max_depth=2}))
  77.         if recipientUsername then
  78.             chatBox.sendMessageToPlayer(fallbackMsg, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  79.         else
  80.             chatBox.sendMessage(fallbackMsg, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  81.         end
  82.         return
  83.     end
  84.  
  85.     local success, err
  86.     if recipientUsername then
  87.         success, err = chatBox.sendFormattedMessageToPlayer(jsonMessage, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  88.     else
  89.         success, err = chatBox.sendFormattedMessage(jsonMessage, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  90.     end
  91.  
  92.     if not success then
  93.         logDebug("Error sending formatted message: " .. (err or "Unknown error") .. ". Attempting fallback to plain text.")
  94.         local plainText = ""
  95.         for _, comp in ipairs(messageComponents) do plainText = plainText .. (comp.text or "") end
  96.         if recipientUsername then
  97.             chatBox.sendMessageToPlayer(plainText, recipientUsername, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  98.         else
  99.             chatBox.sendMessage(plainText, CHAT_BOT_NAME, CHAT_BOT_BRACKETS, CHAT_BOT_BRACKET_COLOR)
  100.         end
  101.     else
  102.         logDebug("Formatted message sent successfully.")
  103.     end
  104.     os.sleep(0.3)
  105. end
  106.  
  107. local function announce(messageComponents)
  108.     sendFormattedChat(messageComponents)
  109. end
  110.  
  111. local function fetchAndTellJoke(requestingUsername)
  112.     logDebug("Fetching a joke. Requested by: " .. (requestingUsername or "Timer"))
  113.     if not http then
  114.         logDebug("HTTP API not available, cannot fetch joke.")
  115.         announce({{text = "Cannot fetch joke: HTTP API is not available on this system.", color = COLORS.RED}})
  116.         return
  117.     end
  118.  
  119.     if not JOKE_API_URL or JOKE_API_URL == "" then
  120.         logDebug("JOKE_API_URL is not configured.")
  121.         announce({{text = "Joke API URL is not configured. Cannot fetch joke.", color = COLORS.RED}})
  122.         return
  123.     end
  124.  
  125.     local response, err, handle = http.get(JOKE_API_URL)
  126.     if not response then
  127.         logDebug("Failed to fetch joke from API. Error: " .. (err or "Unknown HTTP error"))
  128.         announce({{text = "Sorry, I couldn't fetch a joke. Error: ", color = COLORS.RED}, {text = tostring(err or "Is http enabled?"), color = COLORS.YELLOW}})
  129.         return
  130.     end
  131.  
  132.     local responseBody = response.readAll()
  133.     response.close()
  134.     if handle then pcall(function() handle.close() end) end
  135.  
  136.     local success, jokeData = pcall(textutils.unserialiseJSON, responseBody)
  137.     if not success or type(jokeData) ~= "table" then
  138.         logDebug("Failed to parse joke JSON. Error: " .. tostring(jokeData or "Invalid JSON") .. ". Body was: " .. tostring(responseBody))
  139.         announce({{text = "Sorry, I received a malformed joke. (Could not parse API response).", color = COLORS.YELLOW}})
  140.         return
  141.     end
  142.  
  143.     if jokeData.error then
  144.         logDebug("Joke API returned an error. Message: " .. (jokeData.message or "Unknown API error"))
  145.         announce({{text = "The joke API said: \"", color = COLORS.RED}, {text = tostring(jokeData.message or "Something went wrong."), color = COLORS.YELLOW}, {text="\"", color = COLORS.RED}})
  146.         return
  147.     end
  148.  
  149.     if jokeData.type == "single" and type(jokeData.joke) == "string" then
  150.         -- No more prefixes like "Alright..." or "Time for a scheduled joke!"
  151.         local jokeText = jokeData.joke
  152.         local lines = {}
  153.         for line in string.gmatch(jokeText, "[^\r\n]+") do
  154.             table.insert(lines, line)
  155.         end
  156.  
  157.         if #lines == 0 and jokeText ~= "" then
  158.             table.insert(lines, jokeText)
  159.         elseif #lines == 0 and jokeText == "" then
  160.              logDebug("Received an empty joke string from API.")
  161.              announce({{text="The joke I got was... surprisingly short (empty!). Try again?", color=COLORS.YELLOW}})
  162.              return
  163.         end
  164.  
  165.         for i, line in ipairs(lines) do
  166.             -- Joke lines are now GOLD
  167.             announce({{text = line, color = COLORS.GOLD}})
  168.             if i < #lines then
  169.                 os.sleep(0.7)
  170.             end
  171.         end
  172.         logDebug("Joke told: " .. string.gsub(jokeText, "\n", "\\n"))
  173.     else
  174.         logDebug("Joke API returned an unexpected format or missing joke field. Data: " .. textutils.serialize(jokeData))
  175.         announce({{text = "Sorry, the joke I got was in a weird format or incomplete. Try again?", color = COLORS.YELLOW}})
  176.     end
  177. end
  178.  
  179. local function startJokeIntervalTimer()
  180.     if jokeIntervalTimerId then
  181.         os.cancelTimer(jokeIntervalTimerId)
  182.         logDebug("Cancelled existing joke interval timer ID: " .. jokeIntervalTimerId)
  183.         jokeIntervalTimerId = nil
  184.     end
  185.     if jokeIntervalMinutes > 0 then
  186.         jokeIntervalTimerId = os.startTimer(jokeIntervalMinutes * 60)
  187.         logDebug("Joke interval timer started. Duration: " .. jokeIntervalMinutes .. " min. New Timer ID: " .. jokeIntervalTimerId)
  188.     else
  189.         logDebug("Joke interval is 0, timer not started.")
  190.     end
  191. end
  192. --#endregion
  193.  
  194. --#region Command Handlers
  195. local commandHandlers = {}
  196.  
  197. commandHandlers.help = function(username, _)
  198.     logDebug("Executing command: help, User: " .. username)
  199.     announce({{text = "--- JokeBot Commands (".. COMMAND_PREFIX .. ") ---", color = COLORS.GOLD, bold = true}})
  200.     announce({{text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = " - Shows this help message.", color = COLORS.GRAY}})
  201.     announce({{text = COMMAND_PREFIX .. " tell", color = COLORS.AQUA}, {text = " - Tells you a random joke.", color = COLORS.GRAY}})
  202.     announce({{text = COMMAND_PREFIX .. " interval <minutes>", color = COLORS.AQUA}, {text = " - Sets automatic joke interval.", color = COLORS.GRAY}})
  203.     announce({{text = "  (Use 0 for minutes to disable automatic jokes)", color = COLORS.DARK_GRAY}})
  204. end
  205.  
  206. commandHandlers.tell = function(username, _)
  207.     logDebug("Executing command: tell, User: " .. username)
  208.     fetchAndTellJoke(username) -- Pass username for logging, though it's not used in prefix anymore
  209. end
  210.  
  211. commandHandlers.interval = function(username, args)
  212.     logDebug("Executing command: interval, User: " .. username .. ", Args: " .. textutils.serialize(args))
  213.     if #args < 1 then
  214.         announce({{text = "Usage: ", color = COLORS.YELLOW}, {text = COMMAND_PREFIX .. " interval <minutes>", color = COLORS.AQUA}})
  215.         announce({{text = "Current interval: ", color = COLORS.YELLOW}, {text = jokeIntervalMinutes .. " minutes.", color = COLORS.WHITE}})
  216.         return
  217.     end
  218.  
  219.     local minutes = tonumber(args[1])
  220.     if not minutes or minutes < 0 or math.floor(minutes) ~= minutes then
  221.         announce({{text = "Invalid number of minutes. Must be a whole number (0 or greater).", color = COLORS.RED}})
  222.         return
  223.     end
  224.  
  225.     jokeIntervalMinutes = minutes
  226.     if minutes > 0 then
  227.         announce({{text = "Okay, " .. username .. "! I'll tell a joke every ", color = COLORS.GREEN}, {text = minutes .. " minute(s).", color = COLORS.AQUA}})
  228.     else
  229.         announce({{text = "Okay, " .. username .. "! Automatic jokes disabled.", color = COLORS.GREEN}})
  230.     end
  231.     logDebug("Joke interval set to " .. jokeIntervalMinutes .. " minutes by " .. username)
  232.     startJokeIntervalTimer()
  233. end
  234. --#endregion
  235.  
  236. --#region Main Loop
  237. local function run()
  238.     term.clear()
  239.     term.setCursorPos(1, 1)
  240.  
  241.     if DEBUG_MODE then
  242.         local file, err = fs.open(DEBUG_LOG_FILE, "w")
  243.         if file then
  244.             file.write(string.format("[%s] JokeBot Script Initializing - DEBUG MODE ENABLED (v1.1)\n", os.date("%Y-%m-%d %H:%M:%S")))
  245.             file.write("======================================================================\n")
  246.             file.close()
  247.         else
  248.             print("DEBUG LOG ERROR: Could not clear/initialize " .. DEBUG_LOG_FILE .. ": " .. (err or "unknown error"))
  249.         end
  250.     end
  251.     logDebug("Script run() started.")
  252.  
  253.     if not chatBox then
  254.         logDebug("WARNING: Chat Box peripheral ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features will be printed to console only.")
  255.         print("WARNING: Chat Box ('" .. CHAT_BOX_PERIPHERAL_NAME .. "') not found! Chat features disabled/printed to console.")
  256.     end
  257.  
  258.     if not http then
  259.         logDebug("FATAL: HTTP API not available! Joke fetching will not work.")
  260.         print("FATAL: HTTP API is not available on this ComputerCraft system. JokeBot cannot fetch jokes.")
  261.         if chatBox then
  262.              announce({{text = "JokeBot Error: ", color = COLORS.RED, bold=true}, {text="HTTP API is not available. Cannot fetch jokes.", color=COLORS.YELLOW}})
  263.         end
  264.     end
  265.  
  266.     logDebug("Announcing online status.")
  267.     print("JokeBot script started. Type '" .. COMMAND_PREFIX .. " help' in chat or '@all'.")
  268.     if chatBox then
  269.         announce({{text = CHAT_BOT_NAME .. " online!", color = COLORS.GREEN, bold = true}, {text = " Ready to tell some jokes.", color = COLORS.GRAY}})
  270.         announce({{text = "Type '", color = COLORS.GRAY}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' or '@all' for commands.", color = COLORS.GRAY}})
  271.     end
  272.  
  273.     startJokeIntervalTimer()
  274.  
  275.     while true do
  276.         local eventData = {os.pullEvent()}
  277.         local eventType = eventData[1]
  278.         logDebug("Event received: " .. eventType .. " - Data: " .. textutils.serialize(eventData, {compact = true, max_depth = 2}))
  279.  
  280.         if eventType == "chat" then
  281.             local eUsername, eMessage, _, eIsHidden = eventData[2], eventData[3], eventData[4], eventData[5]
  282.             if not eIsHidden and eMessage then
  283.                 -- Check for @all command first
  284.                 if string.lower(eMessage) == "@all" then
  285.                     logDebug("@all command received from " .. eUsername)
  286.                     announce({{text = "Use '", color = COLORS.GREEN}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "' for my commands.", color = COLORS.GREEN}})
  287.                 -- Then check for bot-specific commands
  288.                 elseif string.sub(eMessage, 1, #COMMAND_PREFIX) == COMMAND_PREFIX then
  289.                     logDebug("Chat command received from " .. eUsername .. ": " .. eMessage)
  290.                     local parts = {}
  291.                     for part in string.gmatch(eMessage, "[^%s]+") do
  292.                         table.insert(parts, part)
  293.                     end
  294.  
  295.                     local commandName = ""
  296.                     if parts[2] then commandName = string.lower(parts[2]) end
  297.                     local cmdArgs = {}
  298.                     for i = 3, #parts do table.insert(cmdArgs, parts[i]) end
  299.  
  300.                     logDebug("Parsed command: '" .. commandName .. "', Args: " .. textutils.serialize(cmdArgs))
  301.  
  302.                     if commandHandlers[commandName] then
  303.                         local success, err = pcall(commandHandlers[commandName], eUsername, cmdArgs)
  304.                         if not success then
  305.                             logDebug("Error executing command '" .. commandName .. "': " .. tostring(err))
  306.                             announce({{text = "Oops! Something went wrong while processing your command: ", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW}})
  307.                         end
  308.                     elseif commandName ~= "" then
  309.                         announce({{text = "Unknown command: '", color = COLORS.RED}, {text = commandName, color = COLORS.YELLOW},
  310.                                   {text = "'. Try '", color = COLORS.RED}, {text = COMMAND_PREFIX .. " help", color = COLORS.AQUA}, {text = "'.", color = COLORS.RED}})
  311.                     end
  312.                 end
  313.             end
  314.         elseif eventType == "timer" then
  315.             local timerId = eventData[2]
  316.             logDebug("Timer event received for ID: " .. timerId)
  317.             if timerId == jokeIntervalTimerId then
  318.                 logDebug("Joke interval timer triggered.")
  319.                 fetchAndTellJoke(nil)
  320.                 if jokeIntervalMinutes > 0 then
  321.                     startJokeIntervalTimer()
  322.                 else
  323.                     jokeIntervalTimerId = nil
  324.                     logDebug("Joke interval was 0 after joke, timer stopped.")
  325.                 end
  326.             else
  327.                 logDebug("Unknown timer ID: " .. timerId)
  328.             end
  329.         elseif eventType == "terminate" then
  330.             logDebug("Terminate event received. Shutting down.")
  331.             if jokeIntervalTimerId then
  332.                 os.cancelTimer(jokeIntervalTimerId)
  333.                 logDebug("Joke interval timer cancelled.")
  334.             end
  335.             if chatBox then
  336.                 announce({{text = CHAT_BOT_NAME .. " shutting down...", color = COLORS.YELLOW, bold = true}})
  337.             end
  338.             logDebug("Script terminated.")
  339.             print(CHAT_BOT_NAME .. " terminated.")
  340.             return
  341.         end
  342.     end
  343. end
  344.  
  345. run()
  346. --#endregion
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement