Reinvention of the announcements #4430

Open
opened 2023-05-07 13:54:48 +00:00 by AliasAlreadyTaken · 5 comments

Currently we announce every hour. All of them. That's a wall of text. They need to be set with one long string. So everyone who wants to add or remove a line needs to give the whole string. That's bad, too.

This is the whole code:

local mod_storage = yl_commons.mod_storage

yl_commons.set_announce = mod_storage:get_string("set_announce") or ""

yl_commons.seconds_between_message = 3600
local timer = 0

minetest.register_chatcommand("set_announce", {
    description = "Allows staff to set a new hourly message",
    privs = {
        staff = true,
    },
    func = function(name, param)

        local message = string.gsub(param, "\\n", "\n") or ""

        minetest.log("action", "[yl_commons] (" .. name .. " used setannounce) " .. message)

        yl_commons.set_announce = message

        mod_storage:set_string("set_announce", yl_commons.set_announce)

        timer = yl_commons.seconds_between_message

        return true, "The message due every hour was changed"
    end
})

minetest.register_globalstep(function(dtime)

    timer = timer + dtime

    if timer >= yl_commons.seconds_between_message and yl_commons.set_announce ~= "" then

        -- announce
        minetest.chat_send_all(minetest.colorize("#2b74ff", yl_commons.set_announce))
        -- reset the timer
        timer = 0
    end

end)
Currently we announce every hour. All of them. That's a wall of text. They need to be set with one long string. So everyone who wants to add or remove a line needs to give the whole string. That's bad, too. This is the whole code: ```lua local mod_storage = yl_commons.mod_storage yl_commons.set_announce = mod_storage:get_string("set_announce") or "" yl_commons.seconds_between_message = 3600 local timer = 0 minetest.register_chatcommand("set_announce", { description = "Allows staff to set a new hourly message", privs = { staff = true, }, func = function(name, param) local message = string.gsub(param, "\\n", "\n") or "" minetest.log("action", "[yl_commons] (" .. name .. " used setannounce) " .. message) yl_commons.set_announce = message mod_storage:set_string("set_announce", yl_commons.set_announce) timer = yl_commons.seconds_between_message return true, "The message due every hour was changed" end }) minetest.register_globalstep(function(dtime) timer = timer + dtime if timer >= yl_commons.seconds_between_message and yl_commons.set_announce ~= "" then -- announce minetest.chat_send_all(minetest.colorize("#2b74ff", yl_commons.set_announce)) -- reset the timer timer = 0 end end) ```
AliasAlreadyTaken added the
1. kind/enhancement
label 2023-05-07 13:54:55 +00:00
Author
Owner

We'd like to change that so that we have more control over the announcements.

  • Use modstorage or a JSON file to store announcements and their metadata
  • Use a structure like
{
	id=345,				-- The ID is unique and that's how we address each announcement
	owner="string",		-- Who set this particular announcement?
	time=os.time(),		-- The time the announcement was issues last
	valid=os.time(),	-- The time after which the announcement becomes invalid and cleaned up
	frequency=1-86400,	-- How many seconds should be between each announcement. Default one hour: 3600
	message="string"	-- The message itself
}
  • Implement a chatcommand /announcement_list, which lists all current announcements with ID, owner and message
  • Implement a chatcommand announcement_show ID, which shows one specific announcement with all metadata
  • Implement a chatcommand announcement_delete ID, which removes one specific announcement with ID
  • Implement a chatcommand announcement_set, which adds a new announcement. You could choose $ or # as separators for the parameters
  • Implement a chatcommand announcement_say all|ID which takes a parameter. If this parameter is "all", then show all announcements at once to the public chat. If the parameter is a specific ID, then only show the announcement of this ID.
  • Use a globalstep to calculate when an announcement shall be sent. Announcements of the same frequencyneed to be distributed over the whole time. Example: If there are three announcements that run every hour, they need to run with a 20 minute gap between each of them.
  • Use the globalstep to clean out announcements when their "valid" time ran out.
We'd like to change that so that we have more control over the announcements. * [ ] Use modstorage or a JSON file to store announcements and their metadata * [ ] Use a structure like ``` { id=345, -- The ID is unique and that's how we address each announcement owner="string", -- Who set this particular announcement? time=os.time(), -- The time the announcement was issues last valid=os.time(), -- The time after which the announcement becomes invalid and cleaned up frequency=1-86400, -- How many seconds should be between each announcement. Default one hour: 3600 message="string" -- The message itself } ``` * [ ] Implement a chatcommand `/announcement_list`, which lists all current announcements with ID, owner and message * [ ] Implement a chatcommand `announcement_show ID`, which shows one specific announcement with all metadata * [ ] Implement a chatcommand `announcement_delete ID`, which removes one specific announcement with ID * [ ] Implement a chatcommand `announcement_set`, which adds a new announcement. You could choose $ or # as separators for the parameters * [ ] Implement a chatcommand `announcement_say all|ID` which takes a parameter. If this parameter is "all", then show all announcements at once to the public chat. If the parameter is a specific ID, then only show the announcement of this ID. * [ ] Use a globalstep to calculate when an announcement shall be sent. Announcements of the same frequencyneed to be distributed over the whole time. Example: If there are three announcements that run every hour, they need to run with a 20 minute gap between each of them. * [ ] Use the globalstep to clean out announcements when their "valid" time ran out.
AliasAlreadyTaken added the
2. prio/good first issue
label 2023-05-07 14:10:59 +00:00
AliasAlreadyTaken changed title from Reinvention of the annoucnements to Reinvention of the announcements 2023-05-07 14:22:36 +00:00
Member

If there are two 20-min announcements, they should be shown every 10 minutes? How should they interact with the 1-hour ones?
I feel this system may need some pre-defined time slots, fixed intervals (60/30 min?) and maybe grouping of announcements into one to prevent spam?

If there are two 20-min announcements, they should be shown every 10 minutes? How should they interact with the 1-hour ones? I feel this system may need some pre-defined time slots, fixed intervals (60/30 min?) and maybe grouping of announcements into one to prevent spam?

Hello, here is the 'rewrite' of the code with the requested additions. However I haven't tested it, there may be errors...


local mod_storage = minetest.get_mod_storage()
local announcements = {}

-- Load announcements from mod storage
local function load_announcements()
	announcements = minetest.parse_json(mod_storage:get_string("announcements")) or {}
end

-- Save announcements to mod storage
local function save_announcements()
	mod_storage:set_string("announcements", minetest.write_json(announcements))
end

-- Get the next valid time for an announcement based on its frequency
local function get_next_valid_time(announcement)
	return announcement.time + announcement.frequency * math.ceil((os.time() - announcement.time) / announcement.frequency)
end

-- Sort announcements by their next valid time
local function sort_announcements_by_valid_time()
	table.sort(announcements, function(a, b)
		return get_next_valid_time(a) < get_next_valid_time(b)
	end)
end

-- Get an announcement by its ID
local function get_announcement_by_id(id)
	for i, announcement in ipairs(announcements) do
		if announcement.id == id then
			return announcement, i
		end
	end
end

-- List all current announcements with ID, owner and message
minetest.register_chatcommand("announcement_list", {
	description = "List all current announcements with ID, owner and message",
	privs = { staff = true },
	func = function(name, param)
		local output = {}
		for _, announcement in ipairs(announcements) do
			table.insert(output, "ID: " .. announcement.id .. ", Owner: " .. announcement.owner .. ", Message: " .. announcement.message)
		end
		if #output == 0 then
			return false, "There are no announcements."
		else
			return true, table.concat(output, "\n")
		end
	end,
})

-- Show one specific announcement with all metadata
minetest.register_chatcommand("announcement_show", {
	description = "Show one specific announcement with all metadata",
	privs = { staff = true },
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid ID."
		end
		local announcement = get_announcement_by_id(id)
		if not announcement then
			return false, "Announcement not found."
		else
			local output = "ID: " .. announcement.id .. "\n"
			output = output .. "Owner: " .. announcement.owner .. "\n"
			output = output .. "Time: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.time) .. "\n"
			output = output .. "Valid: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.valid) .. "\n"
			output = output .. "Frequency: " .. announcement.frequency .. " seconds\n"
			output = output .. "Message:\n" .. announcement.message
			return true, output
		end
	end,
})

-- Remove one specific announcement with ID
minetest.register_chatcommand("announcement_delete", {
	description = "Remove one specific announcement with ID",
	privs = { staff = true },
	func = function(name, param)
		local id = tonumber(param)
		if not id then
			return false, "Invalid ID."
		end
		local announcement, i = get_announcement_by_id(id)
		if not announcement then
			return false, "Announcement not found."
		else
			table.remove(announcements, i)
			save_announcements()
			return true, "Announcement removed."
		end
	end,
})

-- Add a new announcement
minetest.register_chatcommand("announcement_set", {
	description = "Add a new announcement",
	privs = {staff = true},
	func = function(name, param)
		local args = param:split("[#%$]")

		if #args ~= 5 then
			return false, "Invalid parameters. Usage: /announcement_set owner#frequency#valid#message"
		end

		-- Parse parameters
		local owner = args[1]
		local frequency = tonumber(args[2])
		local valid = tonumber(args[3])
		local message = args[4]
		local id = os.time()

		-- Create new announcement
		local announcement = {
			id = id,
			owner = owner,
			time = os.time(),
			frequency = frequency,
			valid = valid,
			message = message,
		}

		-- Store new announcement
		table.insert(yl_commons.announcements, announcement)
		yl_commons.save_announcements()

		return true, "Announcement added with ID " .. id
	end,
})

-- Show all announcements
minetest.register_chatcommand("announcement_list", {
	description = "List all announcements",
	privs = {staff = true},
	func = function(name, param)
		local message = "Current announcements:\n"

		for _, announcement in ipairs(yl_commons.announcements) do
			message = message .. "ID: " .. announcement.id .. ", Owner: " .. announcement.owner .. ", Message: " .. announcement.message .. "\n"
		end

		return true, message
	end,
})

-- Show a specific announcement
minetest.register_chatcommand("announcement_show", {
	description = "Show a specific announcement",
	privs = {staff = true},
	func = function(name, param)
		local id = tonumber(param)

		if not id then
			return false, "Invalid ID"
		end

		for _, announcement in ipairs(yl_commons.announcements) do
			if announcement.id == id then
				local message = "ID: " .. announcement.id .. "\n"
				message = message .. "Owner: " .. announcement.owner .. "\n"
				message = message .. "Time: " .. os.date("%c", announcement.time) .. "\n"
				message = message .. "Frequency: " .. announcement.frequency .. " seconds\n"
				message = message .. "Valid until: " .. os.date("%c", announcement.valid) .. "\n"
				message = message .. "Message: " .. announcement.message

				return true, message
			end
		end

		return false, "Announcement not found"
	end,
})

-- Delete an announcement
minetest.register_chatcommand("announcement_delete", {
	description = "Delete an announcement",
	privs = {staff = true},
	func = function(name, param)
		local id = tonumber(param)

		if not id then
			return false, "Invalid ID"
		end

		for i, announcement in ipairs(yl_commons.announcements) do
			if announcement.id == id then
				table.remove(yl_commons.announcements, i)
				yl_commons.save_announcements()

				return true, "Announcement deleted"
			end
		end

		return false, "Announcement not found"
	end,
})


-- Say an announcement
minetest.register_chatcommand("announcement_say", {
	description = "Say an announcement",
	params = "[all|ID]",
	func = function(name, param)
		if param == "all" then
			-- Show all announcements
			for _, announcement in ipairs(yl_commons.announcements) do
				minetest.chat_send_all(minetest.colorize("#2b74ff", "Announcement #" .. announcement.id .. " by " .. announcement.owner .. ": " .. announcement.message))
			end
		else
			-- Show a specific announcement
			local id = tonumber(param)
			if not id then
				return false, "Invalid announcement ID"
			end
			local announcement = yl_commons.get_announcement_by_id(id)
			if not announcement then
				return false, "Announcement not found"
			end
			minetest.chat_send_all(minetest.colorize("#2b74ff", "Announcement #" .. announcement.id .. " by " .. announcement.owner .. ": " .. announcement.message))
		end
		return true, "Announcement displayed"
	end,
})

-- Calculate and send announcements
minetest.register_globalstep(function(dtime)
	for _, announcement in ipairs(yl_commons.announcements) do
		if announcement.frequency > 0 then
			announcement.timer = announcement.timer + dtime
			if announcement.timer >= announcement.frequency then
				minetest.chat_send_all(minetest.colorize("#2b74ff", announcement.message))
				announcement.timer = 0
			end
		end
	end
	-- Clean up old announcements
	local now = os.time()
	for i = #yl_commons.announcements, 1, -1 do
		local announcement = yl_commons.announcements[i]
		if announcement.valid > 0 and now > announcement.valid then
			table.remove(yl_commons.announcements, i)
		end
	end
end)
Hello, here is the 'rewrite' of the code with the requested additions. However I haven't tested it, there may be errors... ```Lua local mod_storage = minetest.get_mod_storage() local announcements = {} -- Load announcements from mod storage local function load_announcements() announcements = minetest.parse_json(mod_storage:get_string("announcements")) or {} end -- Save announcements to mod storage local function save_announcements() mod_storage:set_string("announcements", minetest.write_json(announcements)) end -- Get the next valid time for an announcement based on its frequency local function get_next_valid_time(announcement) return announcement.time + announcement.frequency * math.ceil((os.time() - announcement.time) / announcement.frequency) end -- Sort announcements by their next valid time local function sort_announcements_by_valid_time() table.sort(announcements, function(a, b) return get_next_valid_time(a) < get_next_valid_time(b) end) end -- Get an announcement by its ID local function get_announcement_by_id(id) for i, announcement in ipairs(announcements) do if announcement.id == id then return announcement, i end end end -- List all current announcements with ID, owner and message minetest.register_chatcommand("announcement_list", { description = "List all current announcements with ID, owner and message", privs = { staff = true }, func = function(name, param) local output = {} for _, announcement in ipairs(announcements) do table.insert(output, "ID: " .. announcement.id .. ", Owner: " .. announcement.owner .. ", Message: " .. announcement.message) end if #output == 0 then return false, "There are no announcements." else return true, table.concat(output, "\n") end end, }) -- Show one specific announcement with all metadata minetest.register_chatcommand("announcement_show", { description = "Show one specific announcement with all metadata", privs = { staff = true }, func = function(name, param) local id = tonumber(param) if not id then return false, "Invalid ID." end local announcement = get_announcement_by_id(id) if not announcement then return false, "Announcement not found." else local output = "ID: " .. announcement.id .. "\n" output = output .. "Owner: " .. announcement.owner .. "\n" output = output .. "Time: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.time) .. "\n" output = output .. "Valid: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.valid) .. "\n" output = output .. "Frequency: " .. announcement.frequency .. " seconds\n" output = output .. "Message:\n" .. announcement.message return true, output end end, }) -- Remove one specific announcement with ID minetest.register_chatcommand("announcement_delete", { description = "Remove one specific announcement with ID", privs = { staff = true }, func = function(name, param) local id = tonumber(param) if not id then return false, "Invalid ID." end local announcement, i = get_announcement_by_id(id) if not announcement then return false, "Announcement not found." else table.remove(announcements, i) save_announcements() return true, "Announcement removed." end end, }) -- Add a new announcement minetest.register_chatcommand("announcement_set", { description = "Add a new announcement", privs = {staff = true}, func = function(name, param) local args = param:split("[#%$]") if #args ~= 5 then return false, "Invalid parameters. Usage: /announcement_set owner#frequency#valid#message" end -- Parse parameters local owner = args[1] local frequency = tonumber(args[2]) local valid = tonumber(args[3]) local message = args[4] local id = os.time() -- Create new announcement local announcement = { id = id, owner = owner, time = os.time(), frequency = frequency, valid = valid, message = message, } -- Store new announcement table.insert(yl_commons.announcements, announcement) yl_commons.save_announcements() return true, "Announcement added with ID " .. id end, }) -- Show all announcements minetest.register_chatcommand("announcement_list", { description = "List all announcements", privs = {staff = true}, func = function(name, param) local message = "Current announcements:\n" for _, announcement in ipairs(yl_commons.announcements) do message = message .. "ID: " .. announcement.id .. ", Owner: " .. announcement.owner .. ", Message: " .. announcement.message .. "\n" end return true, message end, }) -- Show a specific announcement minetest.register_chatcommand("announcement_show", { description = "Show a specific announcement", privs = {staff = true}, func = function(name, param) local id = tonumber(param) if not id then return false, "Invalid ID" end for _, announcement in ipairs(yl_commons.announcements) do if announcement.id == id then local message = "ID: " .. announcement.id .. "\n" message = message .. "Owner: " .. announcement.owner .. "\n" message = message .. "Time: " .. os.date("%c", announcement.time) .. "\n" message = message .. "Frequency: " .. announcement.frequency .. " seconds\n" message = message .. "Valid until: " .. os.date("%c", announcement.valid) .. "\n" message = message .. "Message: " .. announcement.message return true, message end end return false, "Announcement not found" end, }) -- Delete an announcement minetest.register_chatcommand("announcement_delete", { description = "Delete an announcement", privs = {staff = true}, func = function(name, param) local id = tonumber(param) if not id then return false, "Invalid ID" end for i, announcement in ipairs(yl_commons.announcements) do if announcement.id == id then table.remove(yl_commons.announcements, i) yl_commons.save_announcements() return true, "Announcement deleted" end end return false, "Announcement not found" end, }) -- Say an announcement minetest.register_chatcommand("announcement_say", { description = "Say an announcement", params = "[all|ID]", func = function(name, param) if param == "all" then -- Show all announcements for _, announcement in ipairs(yl_commons.announcements) do minetest.chat_send_all(minetest.colorize("#2b74ff", "Announcement #" .. announcement.id .. " by " .. announcement.owner .. ": " .. announcement.message)) end else -- Show a specific announcement local id = tonumber(param) if not id then return false, "Invalid announcement ID" end local announcement = yl_commons.get_announcement_by_id(id) if not announcement then return false, "Announcement not found" end minetest.chat_send_all(minetest.colorize("#2b74ff", "Announcement #" .. announcement.id .. " by " .. announcement.owner .. ": " .. announcement.message)) end return true, "Announcement displayed" end, }) -- Calculate and send announcements minetest.register_globalstep(function(dtime) for _, announcement in ipairs(yl_commons.announcements) do if announcement.frequency > 0 then announcement.timer = announcement.timer + dtime if announcement.timer >= announcement.frequency then minetest.chat_send_all(minetest.colorize("#2b74ff", announcement.message)) announcement.timer = 0 end end end -- Clean up old announcements local now = os.time() for i = #yl_commons.announcements, 1, -1 do local announcement = yl_commons.announcements[i] if announcement.valid > 0 and now > announcement.valid then table.remove(yl_commons.announcements, i) end end end) ```
Member

No sanity checks on parameters, will crash if given words instead of numbers, for example.
Broken in several places, can't test it after a couple of small fixes.
Since it does not depend on anything yl_commons really, you could test it as a separate mod and fix it.
And maybe even create a repo for it here?

No sanity checks on parameters, will crash if given words instead of numbers, for example. Broken in several places, can't test it after a couple of small fixes. Since it does not depend on anything yl_commons really, you could test it as a separate mod and fix it. And maybe even create a repo for it here?

Instead of

output = output .. "Owner: " .. announcement.owner .. "\n"
			output = output .. "Time: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.time) .. "\n"
			output = output .. "Valid: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.valid) .. "\n"
			output = output .. "Frequency: " .. announcement.frequency .. " seconds\n"
			output = output .. "Message:\n" .. announcement.message

consider using just one output = output.. with string.format

local template = "Owner: %s\nTime: %s\nValid: %s\nFrequency: %s seconds\nMessage:\n%s"
output = output .. string.format(template, announcement.owner, os.date("%Y-%m-%d %H:%M:%S", announcement.time), os.date("%Y-%m-%d %H:%M:%S", announcement.valid), announcement.frequency, announcement.message)

And just use one date time format

os.date("%Y-%m-%d %H:%M:%S"....
os.date("%c".... --%m-%d-%Y %H:%M:%S
Instead of ```lua output = output .. "Owner: " .. announcement.owner .. "\n" output = output .. "Time: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.time) .. "\n" output = output .. "Valid: " .. os.date("%Y-%m-%d %H:%M:%S", announcement.valid) .. "\n" output = output .. "Frequency: " .. announcement.frequency .. " seconds\n" output = output .. "Message:\n" .. announcement.message ``` consider using just one `output = output..` with string.format ```lua local template = "Owner: %s\nTime: %s\nValid: %s\nFrequency: %s seconds\nMessage:\n%s" output = output .. string.format(template, announcement.owner, os.date("%Y-%m-%d %H:%M:%S", announcement.time), os.date("%Y-%m-%d %H:%M:%S", announcement.valid), announcement.frequency, announcement.message) ``` And just use one date time format ```lua os.date("%Y-%m-%d %H:%M:%S".... os.date("%c".... --%m-%d-%Y %H:%M:%S ```
Sign in to join this conversation.
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: your-land/bugtracker#4430
No description provided.