Advertisement
Myros27

JokeBot V1.2

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