Bad argument #2 to 'gsub' (string/function/table expected)

I’m trying to update my local copy of Macro-Talk and for some reason the following substitution patterns are causing the following error to be triggered.

bad argument #2 to 'gsub' (string/function/table expected)

Simplest example:

	["[Hh]"] = function(unit)
		return UnitHealth(unit).."/"..UnitHealthMax(unit)
	end,

the rest of them are commented out in the full doc below (238-294):

--[[ Text substitutions ]]
local L = MacroTalk_Locals
local SendChatMessage = C_ChatInfo.SendChatMessage or SendChatMessage
local wowmainline = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE)

local subsBase = {
	["%%[Nn]"] = function() return UnitName("player") end,
	["%%[Zz]"] = GetRealZoneText,
	["%%[Ss][Zz]"] = function() 
		local subZ
		if GetSubZoneText() ~= "" then
			subZ = GetSubZoneText()
		else
			subZ = GetRealZoneText()
		end
		return subZ
	end,
	
	["%%[Ll][Oo][Cc]"] = function()
		local mapID = C_Map.GetBestMapForUnit("player")
		if mapID == nil then
			return;
		end
		local position = C_Map.GetPlayerMapPosition(mapID,"player")
		if not position then
			return;
		end
		local x, y = position:GetXY() 
		return ("%0.1f, %0.1f"):format(x*100, y*100)
	end,
	
	["%%[Ww][Pp]"] = function()
		local mapID = C_Map.GetBestMapForUnit("player")
		if mapID == nil then
			return;
		end
		local position = C_Map.GetPlayerMapPosition(mapID,"player")
		if not position then
			return;
		end
		if C_Map.GetUserWaypointHyperlink() == nil then
			C_Map.SetUserWaypoint(UiMapPoint.CreateFromCoordinates(mapID,position:GetXY()))
			return C_Map.GetUserWaypointHyperlink(), C_Map.ClearUserWaypoint()
		else
			return ""
		end
	end,
	
	["%%[Pp][Ii][Nn]"] = function()
		local mapID = C_Map.GetBestMapForUnit("player")
		if mapID == nil then
			return;
		end
		local position = C_Map.GetPlayerMapPosition(mapID,"player")
		if not position then
			return;
		end
		if C_Map.GetUserWaypointHyperlink() == nil then
			return ""
		else
			return C_Map.GetUserWaypointHyperlink()
		end
	end,
	
	["%%[Cc][Oo][Vv]"] = function()
		local covenantID = C_Covenants.GetActiveCovenantID()
		if covenantID == nil then
			return;
		end
		if covenantID == 0 then 
			return "<no covenant>"
		end
		local covenantData = C_Covenants.GetCovenantData(covenantID)
		if covenantData == nil then
			return;
		else
			return covenantData.name
		end
	end,
	
	["%%[Cc][Oo][Vv][Bb]"] = function()
		local covenantID = C_Covenants.GetActiveCovenantID()
		if covenantID == nil then
			return;
		end
		if covenantID == 0 then 
			return;
		end
		local covenantData = C_Covenants.GetCovenantData(covenantID)
		if covenantData == nil then
			return;
		else
			return covenantData.name
		end
	end,
	
	["%%[Rr][Nn][Ll]"] = function()
		local covenantID = C_Covenants.GetActiveCovenantID()
		if covenantID == nil then
			return;
		end
		if covenantID == 0 then 
			return "<no covenant>"
		end
		local renownLevel = C_CovenantSanctumUI.GetRenownLevel()
		if renownLevel == nil then
			return;
		else
			return renownLevel
		end
	end,
	
	["%%[Rr][Nn][Ll][Bb]"] = function()
		local covenantID = C_Covenants.GetActiveCovenantID()
		if covenantID == nil then
			return;
		end
		if covenantID == 0 then 
			return;
		end
		local renownLevel = C_CovenantSanctumUI.GetRenownLevel()
		if renownLevel == nil then
			return;
		else
			return renownLevel
		end
	end,
	
	["%%[Rr][Tt]"] = function()
		local datetime = C_DateAndTime.GetCurrentCalendarTime()
		if datetime == nil then
			return;
		else
			local hours = datetime.hour
			local minutes = datetime.minute
			if hours < 10 then
				hours = "0"..hours
			end
			if minutes < 10 then
				minutes = "0"..minutes
			end
			return hours..":"..minutes
		end
	end,
	
	["%%[Ll][Tt]"] = function()
		local datetime = date("%H:%M")
		if datetime == nil then
			return;
		else
			return datetime
		end
	end,
	
	--[[
	You can add straight text substitutions
	to this table. See the example below. Remove the -- from the front to use
	it. Note also that it's case sensitive unless you write both upper- and
	lower-case letters inside brackets like the substitutions above.]]
	
	--["%%tsinfo"] = "TeamSpeak info: Server: host.domain.com, Password: 12345",
}

local sexes = {
	L["<no %s>"]:format(L["gender"]),
	MALE:lower(),
	FEMALE:lower()
}

local unitInfoUnits = {
	[""] = "player",
	["[Tt]"] = "target",
	["[Ff]"] = "focus",
	["[Mm]"] = "mouseover",
	["[Pp]"] = "pet",
	["[Tt][Tt]"] = "targettarget",
}

local unitInfoSuffixes = {
	[""] = UnitName,
	
	["[Ll]"] = function(unit)
		local level = UnitLevel(unit)
		return level > 0 and level or level < 0 and "??"
	end,
	
	["[Cc]"] =  UnitClass,
	
	["[Cc][Ll]"] = UnitClassification,		
	
	["[Gg]"] = function(unit)
		return sexes[UnitSex(unit)]
	end,
	
	["[Gg][Bb]"] = function(unit)
		local sex = UnitSex(unit)
		return sex > 1 and sexes[UnitSex(unit)] or ""
	end,
	
	["[Rr]"] = function(unit)
		local race
		if UnitIsPlayer(unit) then
			race = UnitRace(unit)
		else
			race = UnitCreatureType(unit)
		end
		return race or L["<no %s>"]:format(RACE:lower())
	end,
	
	["[Rr][Bb]"] = function(unit)
		local race
		if UnitIsPlayer(unit) then
			race = UnitRace(unit)
		else
			race = UnitCreatureType(unit)
		end
		return race or ""
	end,
	
	["[Gg][Uu]"] = function(unit)
		local guild
		guild = GetGuildInfo(unit)
		return guild or "<no guild>"
	end,
	
	["[Gg][Uu][Bb]"] = function(unit)
		local guild
		guild = GetGuildInfo(unit)
		return guild or ""
	end,
	
	["[Rr][Mm]"] = function(unit)
		local nam, realm
		name, realm = UnitName(unit)
		return realm or GetRealmName()
	end,
		
	-- ["[Hh]"] = function(unit)
	-- 	return UnitHealth(unit).."/"..UnitHealthMax(unit)
	-- end,
	
	-- ["[Hh][Pp]"] = function(unit)
	-- 	if (wowmainline) then
	-- 		return ("%.0f%%%%"):format(UnitHealthPercent(unit))
	-- 	else
	-- 		return ("%.0f%%%%"):format(UnitHealth(unit)/UnitHealthMax(unit)*100)
	-- 	end
	-- end,
	
	-- ["[Pp][Ww]"] = function(unit)
	-- 	local pwType, pwTypeTxt = UnitPowerType(unit)
	-- 	local max = UnitPowerMax(unit,pwType)
	-- 	if max > 0 then
	-- 		return UnitPower(unit,pwType).."/"..max
	-- 	else
	-- 		return L["<no %s>"]:format(pwTypeTxt:lower())
	-- 	end
	-- end,
	
	-- ["[Pp][Ww][Bb]"] = function(unit)
	-- 	local max = UnitPowerMax(unit)
	-- 	if max > 0 then
	-- 		return UnitPower(unit).."/"..max
	-- 	else
	-- 		return ""
	-- 	end
	-- end,
	
	-- ["[Pp][Ww][Pp]"] = function(unit)
	-- 	local pwType, pwTypeTxt = UnitPowerType(unit)
	-- 	local max = UnitPowerMax(unit,0)
	-- 	if max > 0 then
	-- 		if (wowmainline) then
	-- 			return ("%.0f%%%%"):format(UnitPowerPercent(unit,pwType))
	-- 		else
	-- 			return ("%.0f%%%%"):format(UnitPower(unit,pwType)/max*100)
	-- 		end
	-- 	else
	-- 		return L["<no %s>"]:format(pwTypeTxt:lower())
	-- 	end
	-- end,
	
	-- ["[Pp][Ww][Pp][Bb]"] = function(unit)
	-- 	local max = UnitPowerMax(unit,0)
	-- 	if max > 0 then
	-- 		if (wowmainline) then
	-- 			return ("%.0f%%%%"):format(UnitPowerPercent(unit))
	-- 		else
	-- 			return ("%.0f%%%%"):format(UnitPower(unit)/max*100)
	-- 		end
	-- 	else
	-- 		return ""
	-- 	end
	-- end,
	
	["[Pp][Ww][Tt]"] = function(unit)
		local pwType, pwTypeTxt = UnitPowerType(unit)
		if pwType == nil then
			return L["<no power type>"]
		else
			return pwTypeTxt:lower()
		end
	end,
	
	["[Pp][Ww][Tt][Bb]"] = function(unit)
		local pwType, pwTypeTxt = UnitPowerType(unit)
		if pwType == nil then
			return ""
		else
			return pwTypeTxt:lower()
		end
	end,
	
	["[Ii][Cc]"] = function(unit)
		local index = GetRaidTargetIndex(unit)
		return index and "{"..(_G["RAID_TARGET_"..index]):lower().."}" or 
			L["<no %s>"]:format(EMBLEM_SYMBOL:lower())
	end,
	
	["[Ii][Cc][Bb]"] = function(unit)
		local index = GetRaidTargetIndex(unit)
		return index and "{"..(_G["RAID_TARGET_"..index]):lower().."}" or ""
	end,
	
	["[Gg][Nn]"] = function(unit)
		local raidN = UnitInRaid(unit) 
		if raidN == nil then
			if unit == "player" then
				return "<not in raid>"
			else
				name, realm = UnitName(unit)
				if realm == nil then
					return "< "..name.." is not in your raid>"
				else
					return "< "..name.."-"..realm.." is not in your raid>"
				end
			end
		else
			local name, rank, subgroup = GetRaidRosterInfo(raidN)
			return subgroup
		end
	end,
	
	["[Gg][Nn][Bb]"] = function(unit)
		local raidN = UnitInRaid(unit) 
		if raidN == nil then
			return ""
		else
			local name, rank, subgroup = GetRaidRosterInfo(raidN)
			return subgroup
		end
	end,
	
	["[Nn][Tt]"] = function(unit)
		if UnitIsVisible(unit) == nil then
			return ""
		else
			local titleName = UnitPVPName(unit)
			return titleName or ""
		end
	end,
	
	["[Uu][Tt]"] = function(unit)
		if UnitIsVisible(unit) == nil then
			return ""
		else
			local titleName = UnitPVPName(unit)
			if titleName == nil then
				return ""
			end	
			local name = UnitName(unit)
			if name == nil then 
				return ""
			end		
			if 	name == titleName then 
				return "<no title>"
			end
			local title = string.gsub(titleName, name, "")
			title = string.gsub(title,",","")
			if string.sub(title,1,1) == " " then
				title = string.sub(title,2)
			end
			if string.sub(title,string.len(title)) == " " then
				title = string.sub(title,1,string.len(title)-1)
			end
			return title
		end
	end,
	
	["[Uu][Tt][Bb]"] = function(unit)
		if UnitIsVisible(unit) == nil then
			return ""
		else
			local titleName = UnitPVPName(unit)
			if titleName == nil then
				return ""
			end	
			local name = UnitName(unit)
			if name == nil then 
				return ""
			end		
			if 	name == titleName then 
				return ""
			end
			local title = string.gsub(titleName, name, "")
			title = string.gsub(title,",","")
			if string.sub(title,1,1) == " " then
				title = string.sub(title,2)
			end
			if string.sub(title,string.len(title)) == " " then
				title = string.sub(title,1,string.len(title)-1)
			end
			return title
		end
	end,
		
}

for initial, unit in pairs(unitInfoUnits) do
	local noUnit = L["<no %s>"]:format(unit)
	for suffix, func in pairs(unitInfoSuffixes) do
		if initial ~= "" or suffix ~= "" then
			local code = "%%"..initial..suffix
			if subsBase[code] then
				error(L["MacroTalk: Conflicting substitutions: "..code])
			end
			subsBase[code] = function()
				return UnitExists(unit) and func(unit) or noUnit
			end
		end
	end
end

local substitutions = {}
local i = 1
for k, v in pairs(subsBase) do
	substitutions[i] = { code = k, func = v }
	i = i + 1
end

sort(substitutions, function(subs1, subs2)
	return subs2.code:len() < subs1.code:len()
end)

local OrigSendChatMessage = SendChatMessage
function C_ChatInfo.SendChatMessage(text, ...)
	for _, substitution in ipairs(substitutions) do
		local func = substitution.func
		text = text:gsub(substitution.code,
			type(func) == "function" and func() or func)
	end
	return OrigSendChatMessage(text, ...)
end

I tried checking type on the return and it shows it’s a string.
I assume it’s something related to secrets but I’m not sure how to go about resolving it.

gsub and .. don’t work with secrets.

try format and https://warcraft.wiki.gg/wiki/API_string.concat

["[Hh]"] = function(unit)
		return format("%s/%s", UnitHealth(unit), UnitHealthMax(unit))
end,

search “concat” at:
https://warcraft.wiki.gg/wiki/Patch_12.0.0/API_changes

That whole restrictions section is a must read :wink:

3 Likes

This is what has killed and will kill a lot of addons. If an addon needs to process, manipulate, or test a value Blizzard has made a secret, there’s not much you can do about it.

It requires extra effort which people will have to evaluate their desire/capicity to put in.

1 Like

Figure it was something like that. Sadly even using format it still breaks, presumably because of the whole gsub thing.

What’s the code?

local OrigSendChatMessage = SendChatMessage
function C_ChatInfo.SendChatMessage(text, ...)
	for _, substitution in ipairs(substitutions) do
		local func = substitution.func
		text = text:gsub(substitution.code,
			type(func) == "function" and func() or func)
	end
	return OrigSendChatMessage(text, ...)
end

It’s at the bottom of the last code bock in the OP

local substitutions = {}
local i = 1
for k, v in pairs(subsBase) do
	substitutions[i] = { code = k, func = v }
	i = i + 1
end

sort(substitutions, function(subs1, subs2)
	return subs2.code:len() < subs1.code:len()
end)

local OrigSendChatMessage = SendChatMessage
function C_ChatInfo.SendChatMessage(text, ...)
	for _, substitution in ipairs(substitutions) do
		local func = substitution.func
		text = text:gsub(substitution.code,
			type(func) == "function" and func() or func)
	end
	return OrigSendChatMessage(text, ...)
end

Probably have to replace the gsub with a string.find, string.sub, string.concat loop or some such shenanigans.

It’s a brave new world.

1 Like

That sounds… not worth the effort.

I’m going through that a tiny bit with my addon, wiki gg and you got me caught up mostly so far!

for

try these API on the vars values and functions:

--//some API to go about resolving it check wiki.gg for more details 
--//https://warcraft.wiki.gg/wiki/Patch_12.0.0/API_changes#Secret_values

--// API issecure
secure = issecure()
isSecure, taint = issecurevariable(tbl, variable)
isSecure, taint = issecurevalue(value)
--// API issecret
isSecret = issecretvalue(value)
isSecretOrContentsSecret = issecrettable(table)
1 Like

I was having the same issue with my patched local copy of Macro-Talk.

I’m not sure how you’re using it, but for my purposes (sending simple chat messages from some of my macros), commenting out the offending lines restored functionality for me. I’m not concerned with pattern replacements within the strings I’m sending.

Let me know if this works for you.

local OrigSendChatMessage = SendChatMessage
function SendChatMessage(text, ...)
--	for _, substitution in ipairs(substitutions) do
--		local func = substitution.func
--		text = text:gsub(substitution.code,
--			type(func) == "function" and func() or func)
--	end
	return OrigSendChatMessage(text, ...)
end

Yeah, I just commented out the patterns that had secrets in them instead. That way I can use the other substitutions. I appreciate the suggestion though.