Startup 2.1.2d

Post finished Clientside Scripts here, known supporting clients for LUA scripting.
Note: Client must be ADC 1.0

BCDC++ | RSX++
Locked
Toast

Startup 2.1.2d

Post by Toast » 28 Feb 2009, 10:14

Code: Select all

--[[ startup.lua: version 2.1.2d 2009/01/22
     modded by ATAG - atagster [at] gmail [dot] com
	 
	2.1.2d:
		fixed: error on missing libsimplepickle.lua - reported by [NH]Nando
		
	2.1.2c: 
		first public release
	
 NMDC Listeners (events):
   	chat		= normal chat message (you won't get your own messages)
   			  function chat( hub, user, "message" )
   			  DISCARDABLE:
   			    * return non-nil to hide the msg from BCDC++
   	unknownchat	= incoming messages displayed without any nick
   			  function unknownchat( hub, "message" )
   			  DISCARDABLE
   	ownChat		= normal chat message from yourself (incoming from the hub)
   			  function ownChat( hub, "message" )
   			  DISCARDABLE
   	pm		= normal pm message
   			  function pm( hub, user, "message" )
   			  DISCARDABLE
   	hubPm		= pm message with a different prefix than the nick in the From field
   			  function hubPm( hub, user, "message which may include a <nickname>" )
   			  DISCARDABLE
   	userConnected 	= a user connected (or rather.. the first getUser call was made)
   			  function userConnected( hub, user )
   	userMyInfo	= a myinfo message from a user, use this as "userConnected"
   			  function userMyInfo( hub, user, "$MyINFO $ALL ... 1234$|" )
   			  DISCARDABLE
   	userQuit	= a quit message from a user
   			  function userQuit( hub, nick )
   			  DISCARDABLE
   	raw		= a full message
   			  function raw( hub, "line" )
   			  DISCARDABLE (message won't get parsed.. no other listeners will be called)
 ADC Listeners:
   	adcChat		= normal chat message (you won't get your own messages).
   			  function adcChat( hub, user, "message", me_msg )
   			  DISCARDABLE:
   			    * return non-nil to hide the msg from BCDC++,
   			    * me_msg is true if a /me-style message is sent
   	adcOwnChat	= normal chat message from yourself (incoming from the hub)
   			  function adcOwnChat( hub, "message", me_msg )
   			  DISCARDABLE
   	adcPm		= normal pm message
   			  function adcPm( hub, user, "message", me_msg )
   			  DISCARDABLE
   	groupPm		= pm message with a different reply-sid than the one who talks (probably chatroom or bot)
   			  function groupPm( hub, user, "message", reply_sid, me_msg )
   			  DISCARDABLE
   	userInf		= an INF message from a user
   			  function userInf( hub, user, flags )
			  flags is a table where all named flags are stored (ie: flags["NI"], flags["I4"], ...)
   			  DISCARDABLE
   	adcUserCon 	= a user connected (or rather.. the first getUser call was made)
   			  function adcUserCon( hub, user )
   	adcUserQui	= a quit message from a user
   			  function adcUserQui( hub, sid, flags )
			  flags is a table where all named flags are stored (ie: flags["ID"], flags["MS"], ...)
 Common Listeners:
   	ownChatOut	= normal chat message from yourself (outgoing to the hub)
   			  function ownChatOut( hub, "message" )
   			  DISCARDABLE
   	connected	= connecting to a hub (many functions may be unavailable (return nil))
   			  function connected( hub )
   	disconnected 	= disconnected from a hub
   			  function disconnected( hub )
   	timer		= called every second (if DC():RunTimer(1) is called), use
   			  dcpp:getHubs()[k]:getAddress() or :getUrl() to select a hub
   			  function timer()
   	clientIn	= peer to peer line oriented transfer control input, userp is a pointer
   			  (lightuserdata)
   			  function clientIn( userp, "line" )
   			  DISCARDABLE (returning non-nil kills the connection)
   	clientOut	= peer to peer line oriented transfer control output, userp is a pointer
   			  (lightuserdata)
   			  function clientOut( userp, "line" )
   			  DISCARDABLE (returning non-nil kills the connection)

 Example listener: 
	function chat( hub, user, text )
		local s = string.lower( text )
		if string.find( s, "[^a-z]bier+[^a-z]" ) or string.find( s, "[^a-z]biertje[^a-z]" ) then
			hub:sendChat( "bier? ja lekker! :)" )
		end
	end

 Example Usercommand:
 	function connected( hub )
		if ( hub:getProtocol() ~= "adc" ) then
			hub:injectChat( '$UserCommand 1 2 Ignore$%[lua:scriptMan:Run("AddUser","!%!{userNI!}")]&#124;|' )
			hub:injectChat( '$UserCommand 1 2 Unignore$%[lua:scriptMan:Run("RmUser","!%!{userNI!}")]&#124;|' )
		end
	end
]]										

DC():PrintDebug( "  ** Started startup.lua **" )

--/////////////////////////////////////
--// Helper functions
--/////////////////////////////////////

if not dcu then
	dcu = {}
end

dcu.NmdcEscape = function(this, msg )
	msg = msg:gsub( "[%$|]", function(s) return "&#"..string.byte(s)..";" end )
	return msg
end

dcu.AdcEscape = function(this, msg, inverse)
	msg = string.gsub(msg, "\r", "")
	local ret = ""
	if inverse then
		local replacetable = {
			["\\\\"] = [[\]],
			["\\s"] = [[ ]],
			["\\n"] = "\n"
		}
		local skip = false
		for k = 1, #msg do
			if skip then
				skip = false
			else
				local c = msg:sub( k, k + 1)
				if replacetable[c] then
					ret = ret .. replacetable[c]
					skip = true
				else
					ret = ret .. c:sub(1, 1)
				end
			end
		end
	else
		local replacetable = {
			["\\"] = [[\\]],
			[" "] = [[\s]],
			["\n"] = [[\n]]
		}
		for k = 1, #msg do
			local c = msg:sub( k, k)
			if replacetable[c] then
				ret = ret .. replacetable[c]
			else
				ret = ret .. c
			end
		end
	end
	return ret
end

--// Checks if the given decimal number has the 2^exp bit set
dcu.BBit = function(this, num, exp)
	if string.find( math.floor(num / 2^exp), "[13579]$") then
		return true
	end
	return false
end

--/////////////////////////////////////
--// Hub manager
--/////////////////////////////////////

if not dcpp or dcpp._init_me_anyway == true then
	dcpp = {}
	dcpp._hubs = {}
	dcpp._listeners = { id = {} }
end

dcpp.addHub = function( this, hub, isitadc )
	if not this._hubs[hub] then
		-- DC():PrintDebug( "Hub added (id = "..tostring( hub )..")" )
		if isitadc then
			this._hubs[hub] = createAdcHub( hub )
			DC():PrintDebug( "ADC hub added (id = " .. tostring( hub ) ..")" )
		else
			this._hubs[hub] = createNmdcHub( hub )
		end
		local h = dcpp:getHub( hub )
		scriptMan:Run( "connected", h )
		
		return this._hubs[hub]
	else
		--DC():PrintDebug( "Tried to add existing hub on: "..this._hubs[hub]:getHubName()..
		--						" (id = "..tostring( hub )..")" )
		return nil
	end
end

dcpp.getHub = function( this, hub )
	if this._hubs[hub] then
		return this._hubs[hub]
	else
		--DC():PrintDebug( "Tried to fetch an unknown hub (id = "..tostring( hub )..")" )
		return this:addHub( hub ):setPartial() -- tell the object that we missed the logon
	end
end

dcpp.getHubs = function( this )
	return this._hubs
end

dcpp.hasHub = function( this, hub )
	if this._hubs[hub] then
		return 1
	else
		return nil
	end
end

dcpp.findHub = function( this, url )
	for k, h in pairs(this._hubs) do
		if h:getUrl() == url then
			return h
		end
	end
	return false
end


dcpp.removeHub = function( this, hub )
	if this._hubs[hub] then
		--DC():PrintDebug( "Hub removed: "..this._hubs[hub]:getHubName().." (id = "..tostring( hub )..")" )
		local h = dcpp:getHub( hub )
		scriptMan:Run( "disconnected", h )

		this._hubs[hub]:destroy()
		this._hubs[hub] = nil
	else
		--DC():PrintDebug( "Tried to remove non-existent hub (id = "..tostring( hub )..")" )
	end		
end

dcpp.listHubs = function( this )
	DC():PrintDebug( "** Listing hubs **" )
	for k,v in pairs(this._hubs) do
		DC():PrintDebug( tostring( k ).." ("..v:getHubName()..")" )
	end
end

dcpp.setListener = function( this, ltype, id, func  )
	-- load func into the caller's environment
	getfenv(2)[ltype] = func
end

dcpp.getListeners = function( this, ltype )
-- only for backward compatibility :)
end

--/////////////////////////////////////
--// Hub object
--/////////////////////////////////////

function createAdcHub( hubid )
	local hub = {}
	
	hub._id = hubid
	hub._mySID = nil
	hub._myNick = nil
	hub._myCID = nil
	hub._nick = nil
	hub._description = nil
	hub._users = {}
	hub._uptime = os.time()
	
	hub.getId = function( this )
		return this._id
	end
	
	hub.getUptime = function( this )
		return ( os.time() - hub._uptime ) / 60
	end
	
	hub.getProtocol = function( this )
		return "adc"
	end
	
	hub.getAddress = function( this )
		return DC():GetHubIpPort( this._id )
	end
	
	hub.getHubName = function( this )
		--// TODO: Shall we return with NI or DE? or both?
		--//       For now just returning the nick with the url
		if this._nick then
			return this._nick  .. " ("..this:getUrl()..")"
		else
			return this:getUrl()
		end
	end
	
	hub.getUrl = function( this )
		return DC():GetHubUrl( this._id )
	end
	
	hub.getOwnNick = function( this )
		return this._myNick
	end

	hub.setOwnNick = function( this, newnick )
		this._myNick = newnick
		DC():PrintDebug( " [STATUS] Own nick set: " .. newnick )
	end
	
	hub.getOwnSid = function( this )
		if this._mySID then
			return this._mySID
		end
		DC():PrintDebug( " [" .. this:getUrl() .."] Own SID not set, your scripts are failing. Reconnect please." )
	end
	
	hub.getOwnCid = function( this )
		if this._myCID then
			return this._myCID
		end
		DC():PrintDebug( " [" .. this:getUrl() .."] Own CID not set, your scripts are failing. Reconnect please." )
	end
	
	hub.getUser = function( this, sid, inf )
		local newuser = false
		if sid == "HUB9" and inf then
			-- INF is about the hub itself
			local params = {}
			string.gsub(inf, "([^ ]+)", function(s) table.insert(params, s) end )
			for k in pairs(params) do
				local name = string.sub( params[k], 1, 2 )
				if name == "NI" then
					local NI = dcu:AdcEscape(string.sub( params[k], 3), true)
					this._nick = NI
					DC():PrintDebug(" [STATUS] HUB nick set: " .. NI )
				elseif name == "DE" then
					local DE = dcu:AdcEscape(string.sub( params[k], 3), true)
					this._description = DE
					DC():PrintDebug(" [STATUS] HUB description set: " .. DE )
				end
			end
		end
		
		if not this._users[sid] then
			this._users[sid] = createAdcUser( this, sid )
			newuser = true
			if this:getUptime() >= 2 then -- start sending messages AFTER logon
	            		scriptMan:Run( "adcUserCon", this, this._users[sid] )
			end
		end
		local r = this._users[sid]
		if inf then
			local params = {}
			string.gsub(inf, "([^ ]+)", function(s) table.insert(params, s) end )
			for k in pairs(params) do
				local name = string.sub( params[k], 1, 2 )
				if name == "ID" then
					local ID = string.sub( params[k], 3)
					r:setCid(ID)
					if sid == this._mySID then
						this._myCID = ID
						DC():PrintDebug(" [STATUS] Own CID set: " .. ID )
					end
				elseif name == "CT" then
					local CT = string.sub( params[k], 3)
					r:processCt(CT)
				elseif name == "I4" then
					local I4 = string.sub( params[k], 3)
					r:setIp(I4)
				elseif name == "NI" then
					local NI = dcu:AdcEscape(string.sub( params[k], 3), true)
					if not newuser then
						local oldNI = r:getNick()
						r:setNick(NI)						
						this:addLine( oldNI .. " is now known as " .. NI )
					else
						r:setNick(NI)
					end
					if sid == this._mySID then
						this:setOwnNick(NI)
					end
				end
			end
		end
		return r
	end
	
	hub.removeUser = function( this, sid )
		this._users[sid] = nil
	end
	
	hub.isOp = function( this, sid )
		if this._users[sid] then
			return this._users[sid]._op
		end
		return nil
	end
	
	hub.getSidbyNick = function( this, nick )
		for k in pairs(this._users) do
			if this._users[k]._nick == nick then
				return k
			end
		end
		return nil
	end
	
	hub.getSidbyCid = function( this, cid )
		for k in pairs(this._users) do
			if this._users[k]._cid == cid then
				return k
			end
		end
		return nil
	end
	
	hub.sendChat = function( this, msg )
		local ownSid = this:getOwnSid()
		msg = dcu:AdcEscape( msg )
		DC():SendHubMessage( this:getId(), "BMSG " .. ownSid .. " " .. msg .. "\n" )
	end
	
	hub.injectChat = function( this, msg )
			DC():PrintDebug(" [WARNING] Your scripts trying to use hub:injectChat on ADC hub. Please use hub:injectMessage() to inject an ADC message or hub:addLine() to inject a chat line." )
			-- hub:injectMessage( msg )
	end
	
	hub.injectMessage = function( this, msg )
		DC():InjectHubMessageADC( this:getId(), msg )
	end
	
	hub.addLine = function( this, msg, fmt )
		--// TODO: need a function which adds a chat line without nick
		msg = dcu:AdcEscape( msg )
		DC():InjectHubMessageADC( this:getId(), "ISTA 000 " .. msg )
	end
	
	hub.sendPrivMsgTo = function( this, victimSid, msg_unescaped, hideFromSelf )
		local ownSid = this:getOwnSid()
		local msg = dcu:AdcEscape( msg_unescaped )
		if ownSid then
			if hideFromSelf then
				DC():SendHubMessage( this:getId(), "DMSG ".. ownSid .. " " .. victimSid .." " .. msg .. " PM" .. ownSid .."\n" )
			else
				DC():SendHubMessage( this:getId(), "EMSG ".. ownSid .. " " .. victimSid .." " .. msg .. " PM" .. ownSid .."\n" )
			end
		end	
	end

	hub.injectPrivMsg = function( this, victimSid, fromSid, msg )
		DC():InjectHubMessageADC( this:getId(), "DMSG " .. fromSid .." ".. victimSid .." ".. msg .. " PM" .. victimSid .. "\n" )
	end
	
	hub.findUsers = function( this, nick, notag )
		-- you get a possibly empty table of users
		if not notag then
			return { this._users[this:getSidbyNick(nick)] }
		else
			local list = {}
			for k in pairs(this._users) do
				local ret,c,n = string.find( this._users[k]._nick, "^%[.*%](.-)$" )
				if n == nick then
					table.insert( list, this._users[k] )
				end
			end
			return list
		end
	end

	hub.destroy = function( this )
	end
	
	--// events
	
	hub.onChatMessage = function( this, user, text, me_msg )
		return scriptMan:Run( "adcChat", this, user, text, me_msg )
	end
	
	hub.onChatFromSelf = function( this, text, me_msg )
		return scriptMan:Run( "adcOwnChat", this, text, me_msg )
	end	
	
	hub.onINF = function( this, user, flags )
		return scriptMan:Run( "userInf", this, user, flags )
	end
	
	hub.onQUI = function( this, sid, flags )
		scriptMan:Run( "adcUserQui", this, sid, flags )
	end
	
	hub.onPrivateMessage = function( this, user, targetSid, replySid, text, me_msg )
		if targetSid == this:getOwnSid() then
			if user:getSid() == replySid then
				return scriptMan:Run( "adcPm", this, user, text, me_msg )
			else
				return scriptMan:Run( "groupPm", this, user, replySid, text, me_msg )
			end	
		end
	end
	
	hub.attention = function( this )
		DC():HubWindowAttention( this:getId() )
	end

	return hub
end

function createNmdcHub( hubid )
	local hub = {}

	hub._id = hubid
	hub._users = {}
	hub._name = nil
	hub._myNick = nil
	hub._partial = nil
	hub._gotOpList = nil
	hub._uptime = os.time()
	hub._hubUC = {}
	hub._customUC = {}

	hub.getId = function( this )
		return this._id
	end
	
	hub.getProtocol = function( this )
		return "nmdc"
	end

	hub.getUptime = function( this )
		return ( os.time() - hub._uptime ) / 60
	end

	hub.getUser = function( this, nick, op )
		if not this._users[nick] then
			this._users[nick] = createNmdcUser( this, nick )
			if this:getUptime() >= 2 then -- start sending messages AFTER logon
	            		scriptMan:Run( "userConnected", this, this._users[nick] )
			end
		end
		local r = this._users[nick]
		if op then
			r:setOp( true )
			r:setClass( "op" )
			this._gotOpList = 1
		end
		return r
	end

	hub.findUsers = function( this, nick, notag )
		-- you get a possibly empty table of users
		if not notag then
			return { this._users[nick] }
		else
			local list = {}
			for k,v in pairs(this._users) do
				local ret,c,n = string.find( k, "^%[.*%](.-)$" )
				if n == nick then
					table.insert( list, v )
				end
			end
			return list
		end
	end

	hub.gotOpList = function( this )
		return this._gotOpList
	end

	hub.isOp = function( this, nick )
		if this._users[nick] and this._users[nick]:isOp() then
			return 1
		end
	end

	hub.removeUser = function( this, nick )
		this._users[nick] = nil
	end

	hub.setPartial = function( this )
		--// we're most likely missing logon info
		this._partial = 1
	end

	hub.isPartial = function( this )
		--// are we missing logon info?
		return this._partial
	end	

	hub.setHubName = function( this, msg )
		this._name = msg
	end

	hub.getAddress = function( this )
		return DC():GetHubIpPort( this._id )
	end
	
	hub.getUrl = function( this )
		return DC():GetHubUrl( this._id )
	end

	hub.getHubName = function( this )
		if this._name then
			return this._name.." ("..this:getUrl()..")"
		else
			return this:getUrl()
		end
	end

	hub.setOwnNick = function( this, nick )
		this._myNick = nick
	end

	hub.getOwnNick = function( this )
		if not this:isPartial() then
 			return this._myNick
 		end
		DC():PrintDebug( "[" .. this:getUrl() .."] Your scripts are failing. "..
					"Own nick not set. Reconnect please." )
	end

	hub.destroy = function( this )
	end

	hub.sendChat = function( this, msg )
		local ownNick = this:getOwnNick()
		msg = dcu:NmdcEscape( msg )
		if ownNick then
			DC():SendHubMessage( this:getId(), "<"..ownNick.."> "..msg.."|" )
		end
	end

	hub.injectChat = function( this, msg, skipusercommand )
			DC():InjectHubMessage( this:getId(), msg )
			-- test if it's usercommand
			if ( string.sub(msg, 1, 13) == "$UserCommand " ) and (not skipusercommand) then
				this:customUC( msg )
			end
	end
	
	hub.addLine = function( this, msg, fmt )
		if not fmt then
			msg = "*** " .. msg
		end
		DC():InjectHubMessage( this:getId(), msg )
	end

	hub.sendPrivMsgTo = function( this, victim, msg, hideFromSelf )
		local ownNick = this._myNick
		msg = dcu:NmdcEscape( msg )
		if ownNick then
			DC():SendHubMessage( this:getId(), "$To: "..victim.." From: "..ownNick.." $"..msg.."|" )
			if not hideFromSelf then
				this:injectPrivMsg( victim, ownNick, msg )
			end
		end	
	end

	hub.injectPrivMsg = function( this, from, to, msg )
			DC():InjectHubMessage( this:getId(), "$To: "..to.." From: "..from.." $"..msg )
	end

	hub.injectPrivMsgFmt = function( this, from, to, msg )
		this:injectPrivMsg( from, to, "<"..from.."> "..msg )
	end

	hub.attention = function( this )
		DC():HubWindowAttention( this:getId() )
	end

	--///////////////////////////
	--// $UserCommand manager
	--///////////////////////////
	-- Todo: context sensitivitiy at UC 255

	hub.hubUC = function( this, msg )
		local uc_type = string.gsub( msg, "%$UserCommand (%d+) .+", "%1")
		-- DC():PrintDebug( "UserCommand " .. uc_type .. " arrived from " .. this:getHubName() .. " : " .. msg)
		if uc_type == "255" then
			-- clear user command list and resend our own commands to prevent it from disappearing
			-- DC():PrintDebug( "Clearing Hub UC list.." )
			this:clearHubUCList()
			-- DC():PrintDebug( "Resending Custom commands.." )
			this:reSendCustomUC()
		else
			-- add usercommands to UClist
			-- DC():PrintDebug( "Adding hubUC.." )
			this:addHubUC( msg )
		end

		return true
	end

	hub.customUC = function ( this, msg )
		local uc_type = string.gsub( msg, "%$UserCommand (%d+) .+", "%1")
		-- DC():PrintDebug( "UserCommand " .. uc_type .. " arrived from a script: " .. msg)
		if uc_type == "255" then
			-- clear own user command list and resend hub usercommands to prevent it from disappearing
			this:clearCustomUCList()
			this:reSendHubUC()
		else
			-- add usercommands to UClist
			this:addCustomUC( msg )
		end
		return true
	end

	hub.addHubUC = function( this, msg )
		table.insert( this._hubUC, msg )
		return true
	end

	hub.addCustomUC = function( this, msg)
		table.insert( this._customUC, msg )
		return true
	end

	hub.clearHubUCList = function( this )
		this._hubUC = {}
		return true
	end

	hub.clearCustomUCList = function( this )
		this._customUC = {}
		return true
	end
	
	hub.reSendCustomUC = function( this )
		for k in pairs(this._customUC) do
			hub:injectChat( this._customUC[k] , true )
			-- DC():PrintDebug("Sending custom uc from stored table: " .. this._customUC[k] )
		end
		return true
	end

	hub.reSendHubUC = function( this )
		for k in pairs(this._hubUC) do
			hub:injectChat( this._hubUC[k] , true)
		end
		return true
	end

	--////////////////
	--// Own functions
	--////////////////
	
	hub.onRaw = function( this, msg )
		return scriptMan:Run( "raw", this, msg )
	end

	hub.onSearch = function( this, msg )
		--DC():PrintDebug( this:getHubName().."> "..msg )
	end
	
	hub.onHello = function( this, user, msg )
	end
	
	hub.onMyInfo = function( this, user, msg )
		return scriptMan:Run( "userMyInfo", this, user, msg )
	end
	
	hub.onQuit = function( this, nick, msg )
		return scriptMan:Run( "userQuit", this, nick )
	end
	
	hub.onHubName = function( this, hubname, msg )
	end
	
	hub.onPrivateMessage = function( this, user, to, prefix, text, full )
		if to == this:getOwnNick() then
			if prefix == "<"..user:getNick().."> " then
				return scriptMan:Run( "pm", this, user, text )
			else
				return scriptMan:Run( "hubPm", this, user, text )
			end	
		end
	end

	hub.onChatMessage = function( this, user, text )
		return scriptMan:Run( "chat", this, user, text ) or scriptMan:Run( "ChatArrival", this, user, text )
	end
	
	hub.onUnknownChatMessage = function( this, text )
		return scriptMan:Run( "unknownchat", this, text )
	end
	
	hub.onChatFromSelf = function( this, text )
		return scriptMan:Run( "ownChat", this, text )
	end	

	return hub
end



--/////////////////////////////////////
--// User object
--/////////////////////////////////////

function createNmdcUser( hub, nick )
	local user = {}

	user._hub = hub
	user._nick = nick
	user._op = false
	user._ip = ""
	user._class = "user"
	user._handled_messages = {} -- flood protection
	setmetatable( user, {__index = {
		sNick = user._nick,
		bOperator = function() return user._op end,
		sIP = function() return user._ip end,
		}
	})
	user.getProtocol = function( this )
		return "nmdc"
	end

	user.setOp = function( this, op )
		this._op = op
		return this
	end

	user.isOp = function( this )
		return this._op
	end
	
	user.setClass = function( this, param )
		this._class = param
		return this
	end
	
	user.getClass = function( this )
		return this._class
	end

	user.setIp = function( this, ip )
		this._ip = ip
	end

	user.getIp = function( this )
		return this._ip
	end
	
	user.getNick = function( this )
		return this._nick
	end

	user.sendPrivMsgFmt = function( this, msg, hideFromSelf )
		local ownNick = this._hub:getOwnNick()
		if ownNick then
			this._hub:sendPrivMsgTo( this._nick, "<"..ownNick.."> "..msg, hideFromSelf )
		end
		return this
	end

	user.setMsgHandled = function( this, which )
		this._handled_messages[which] = 1
		return this
	end

	user.msgHandled = function( this, which )
		return this._handled_messages[which]
	end

	return user
end

function createAdcUser( hub, sid )
	local user = {}
	
	user._sid = sid
	user._cid = ""
	user._hub = hub
	user._nick = ""
	user._op = false
	user._bot = false
	user._registered = false
	user._hubitself = false
	user._class = "user"
	user._ip = ""
	user._handled_messages = {} -- flood protection
	
	user.getProtocol = function( this )
		return "adc"
	end

	user.setOp = function( this, op )
		this._op = op
		return this
	end

	user.isOp = function( this )
		return this._op
	end
	
	user.processCt = function( this, param )
		local num = tonumber(param)
		if num then
			
			--// Init
			this._class = "user"
			
			--// 2^0: bot
			if dcu:BBit(num, 0) then
				this:setBot(true)
				DC():PrintDebug("BOT set: " .. this:getSid())
			else
				this:setBot(false)
			end
			
			--// 2^1: registered
			if dcu:BBit(num, 1) then
				this:setReg(true)
			else
				this:setReg(false)
			end
			
			--// 2^2, 2^3, 2^4: some type of operator
			if dcu:BBit(num, 2) or dcu:BBit(num, 3) or dcu:BBit(num, 4) then
				this:setOp(true)
				if dcu:BBit(num, 2) then
					this._class = "op"
				elseif dcu:BBit(num, 3) then
					this._class = "su"
				else
					this._class = "owner"
				end
			else
				this:setOp(false)
			end
			
			if dcu:BBit(num, 5) then
				this:setHub(true)
				this._class = "hub"
			else
				this:setHub(false)
			end
			
		else
			--// Empty CT field should cause all properties gone
			this:setBot(false)
			this:setOp(false)
			this:setReg(false)
			this:setHub(false)
			this._class = "user"
		end
		
		DC():PrintDebug("CLASS: " .. this._class .. " [" .. tostring(param) .. "]" )
		return this
	end
	
	--// Possible values: "user", "op", "su", "owner", "hub"
	user.getClass = function( this )
		return this._class
	end
	
	user.setBot = function(this, bot)
		this._bot = bot
		return this
	end
	
	user.isBot = function(this)
		return this._bot
	end
	
	user.setReg = function(this, registered)
		this._registered = registered
		return this
	end
	
	user.isReg = function(this)
		return this._registered
	end
	
	user.setHub = function(this, hubitself)
		this._hubitself = hubitself
		return this
	end
	
	user.isHub = function(this)
		return this._hubitself
	end

	user.setIp = function( this, ip )
		this._ip = ip
	end

	user.getIp = function( this )
		return this._ip
	end
	
	user.setCid = function( this, cid )
		this._cid = cid
	end
	
	user.getCid = function( this )
		return this._cid
	end
	
	user.getSid = function( this )
		return this._sid
	end
	
	user.getNick = function( this )
		return this._nick
	end
	
	user.setNick = function( this, nick )
		this._nick = nick
		-- DC():PrintDebug("Nick set: " .. nick )
	end
	
	user.sendPrivMsg = function( this, msg, hideFromSelf )
		local victimSid = this:getSid()
		this._hub:sendPrivMsgTo( victimSid, msg, hideFromSelf )
		return this
	end
	
	-- Backward compatibility
	user.sendPrivMsgFmt = user.sendPrivMsg

	user.setMsgHandled = function( this, which )
		this._handled_messages[which] = 1
		return this
	end

	user.msgHandled = function( this, which )
		return this._handled_messages[which]
	end
	
	return user
end


--/////////////////////////////////////
--// Handlers
--/////////////////////////////////////

nmdch = {}

function nmdch.DataArrival( hub, msg )
	local h = dcpp:getHub( hub )

	--// If we missed the logon, we really have no business here,
	--// modify only if you really have to.
	--// Note that if not h:isPartial and not h:getOwnNick(),
	--// functions requiring an ownNick will silently fail.
	if h:isPartial() then
		return
	end

	--// raw/unparsed message
	if h:onRaw( msg ) then
		return scriptMan:Run( "UnknownArrival", msg )
	end

	--// parse message and fire appropriate 
	local ret,c,cmd = string.find( msg, "^%$([^ ]+)" )
	if ret then
		if cmd == "Search" then
			return h:onSearch( msg )
		elseif cmd == "Hello" then
			local nick = string.sub( msg, 8 )
			if not h:getOwnNick() then
				h:setOwnNick( nick ) -- don't trust this nick on h:isPartial()
			end
			return h:onHello( h:getUser( nick ), msg )
		elseif cmd == "MyINFO" and string.sub( msg, 1, 13 ) == "$MyINFO $ALL " then
			local nick = string.sub( msg, 14, string.find( msg, " ", 14, 1 ) - 1 )
			return h:onMyInfo( h:getUser( nick ), msg )
		elseif cmd == "Quit" then
			local nick = string.sub( msg, 7 )
			h:removeUser( nick )
			return h:onQuit( nick, msg )
		elseif cmd == "HubName" then
			local hubname = string.sub( msg, 10 )
			h:setHubName( hubname )
			return h:onHubName( hubname, msg )
		elseif cmd == "OpList" then
			for nick in string.gfind( string.sub( msg, 9 ), "[^$]+") do
				h:getUser( nick, 1 )
			end
			return nil
		elseif cmd == "UserIP" then
			local nick,ip
			for combo in string.gfind( string.sub( msg, 9 ), "[^$]+") do
				ret,c,nick,ip = string.find( combo, "^(%S+) (%S+)$" )
				if ret then
					h:getUser( nick ):setIp( ip )
				end
			end
			return nil
		elseif cmd == "UserCommand" then
			h:hubUC( msg )
		--elseif string.sub( msg, 1, 10 ) == "$NickList " then
		--	for nick in string.gfind( string.sub( msg, 9, -1), "[^$]+") do
		--		h:getUser( nick )
		--	end
		--	return nil
		elseif cmd == "To:" then
			local ret,c,to,from,fulltext = string.find( msg, "^%$To: ([^ ]+) From: ([^ ]+) %$(.*)$" )
			if ret then
				local ret,c,prefix,text = string.find( fulltext, "^(%b<> )(.*)$" )
				if ret then
					return h:onPrivateMessage( h:getUser( from ), to, prefix, text, msg )
				else
					return h:onPrivateMessage( h:getUser( from ), to, nil, fulltext, msg )
				end
			end
		end
	elseif string.sub( msg, 1, 1 ) == "<" then
		local ret,c,nick,text = string.find( msg, "^<([^>]+)> (.*)$" )
		if ret and h:getOwnNick() then
			if nick ~= h:getOwnNick() then -- don't be flooding mainchat now..
 				return h:onChatMessage( h:getUser( nick ), text )
			else
				return h:onChatFromSelf( text )
			end
		end
	elseif msg ~= "" then
		return h:onUnknownChatMessage(msg)
	end
end

function dcpp.UserDataIn( user, msg )
	return scriptMan:Run( "clientIn", user, msg )
end

function dcpp.UserDataOut( user, msg )
	return scriptMan:Run( "clientOut", user, msg )
end

function dcpp.OnCommandEnter( hub, text )
	local h = dcpp:getHub( hub )
	return scriptMan:Run( "ownChatOut", h, text )
end

function dcpp.OnTimer()
	-- Called every second.
	return scriptMan:Run( "timer" )
end

function nmdch.OnHubAdded( hub )
	dcpp:addHub( hub, false )
end

function nmdch.OnHubRemoved( hub )
	dcpp:removeHub( hub )
end

adch = {}

function adch.DataArrival( hub, msg )
	if msg == "" then
		return nil
	end
	local h = dcpp:getHub( hub )
	
	local params = {}
	string.gsub(msg, "([^ ]+)", function(s) table.insert(params, s) end )
	
	local mtype = string.sub(params[1], 1, 1)
	local cmd = string.sub(params[1], 2, 4)
	local sid, targetSid, parameters = false, false, ""
	
	if mtype == "I" then
		parameters = string.sub( msg, 6)
		sid = "HUB9"
	elseif mtype == "B" then
		parameters = string.sub( msg, 11)
		sid = params[2]
	elseif mtype == "D" or mtype == "E" then
		parameters = string.sub( msg, 16 )
		sid = params[2]
		targetSid = params[3]
	end
	
	-- Building flags table
	local flags = {}
	string.gsub(parameters, "([^ ]+)", function(s) flags[string.sub(s, 1, 2)] = dcu:AdcEscape( string.sub(s, 3), true ) end )
	
	if cmd == "SID" then
		DC():PrintDebug( "[STATUS] SID received: " .. params[2] )
		h._mySID = params[2]
	elseif cmd == "QUI" then
		h:removeUser( params[2] )
		h:onQUI( params[2], flags )
	elseif cmd == "INF" then
		local u = h:getUser( sid, parameters )
		return h:onINF( u, flags )
	elseif cmd == "MSG" then
		local pm, replySid, me_msg = false, false, false
		local tmp = {}
		
		string.gsub(parameters, "([^ ]+)", function(s) table.insert(tmp, s) end )
		local text = dcu:AdcEscape(tmp[1], true)
		tmp[1] = nil
		
		-- Check for named parameters
		for k in pairs(tmp) do
			local name = string.sub(tmp[k], 1,2)
			local value = string.sub(tmp[k], 3)
			if name == "ME" then
				if value == "1" then
					me_msg = true
				end
			elseif name == "PM" then
				pm = true
				replySid = value
			end
		end
		
		if pm then
			return h:onPrivateMessage( h:getUser( sid ), targetSid, replySid, text, me_msg )
		else
			if sid ~= h:getOwnSid() then -- don't be flooding mainchat now..
				return h:onChatMessage( h:getUser( sid ), text, me_msg )
			else
				return h:onChatFromSelf( text, me_msg )
			end
		end
		
	end
end

function adch.OnHubAdded( hub )
	dcpp:addHub( hub, true )
end

function adch.OnHubRemoved( hub )
	dcpp:removeHub( hub )
end


--/////////////////////////////////////
--// Utility functions
--/////////////////////////////////////

function SendActiveSearchResult( hub, ip_port, search_nick, filename, filesize, open_slots, total_slots )
	DC():SendUDP( ip_port, "$SR "..search_nick.." "..filename.."\005".. filesize.." "..open_slots..
		"/"..total_slots.."\005"..dcpp:getHub( hub ):getHubName() ) -- no pipe in UDP $SR
end



--/////////////////////////////////////
--// Start Lua Manager
--/////////////////////////////////////

local fnc, err = loadfile( DC():GetAppPath().."scripts\\libsimplepickle.lua" )
if fnc then
	fnc()
	fnc = nil
else
	DC():PrintDebug( " LUA ERROR: "..err)
	pickle = { store = function() end }
end

scriptMan = {}

scriptMan.Init = function( filename )
	dcpp._listeners = { id = {} }
	scriptMan.script = {}
	setmetatable( scriptMan.script, { __index = {
		load = function( this, filename )
			local ret
			if scriptMan.script:getid( filename ) then
				scriptMan.script:unload( filename )
				ret = "successfully reloaded."
			else
				ret = "successfully loaded."
			end
			local i = #dcpp._listeners.id +1
			local func, err = loadfile( DC():GetAppPath().."scripts\\"..filename )
			if not func then return nil, err end
			-- load into a new environment
			dcpp._listeners[i] = {}
			setmetatable( dcpp._listeners[i], { __index = _G } )
			setfenv( func, dcpp._listeners[i] )
			func()

			dcpp._listeners.id[i] = filename
			-- change status flag if exists
			local known = false
			for i in ipairs(scriptlist) do
				if scriptlist[i][1] == filename then
					scriptlist[i][2] = 1
					known = true
					break
				end
			end
			-- insert if it's new
			if not known then table.insert( scriptlist, { filename, 1 } ) end
			scriptMan.script:save()
			return ret
		end,
		remove = function( this, filename )
			local id = scriptMan.script:getid( filename )
			if id then
				table.remove( dcpp._listeners.id, id ) -- remove its id
				table.remove( dcpp._listeners, id ) -- remove its environment
				collectgarbage("collect")
			end
			-- remove from scriptlist
			for i in ipairs(scriptlist) do
				if scriptlist[i][1] == filename then
					table.remove( scriptlist, i )
					scriptMan.script:save()
					return "removed."
				end
			end
			return "not found."
		end,
		unload = function( this, filename )
			local id = scriptMan.script:getid( filename )
			if id then
				table.remove( dcpp._listeners.id, id ) -- remove its id
				table.remove( dcpp._listeners, id ) -- remove its environment
				collectgarbage("collect")
			end
			-- change status flag to 0 (inactive)
			for i in ipairs(scriptlist) do
				if scriptlist[i][1] == filename then
					scriptlist[i][2] = 0
					scriptMan.script:save()
					return "unloaded."
				end
			end
			return "not found"
		end,
		getid = function( this, filename )
			for id, name in pairs( dcpp._listeners.id ) do
				if name == filename then
					return id
				end
			end
			return nil
		end,
		save = function( this )
			pickle.store( DC():GetAppPath().."scripts\\scriptlist.tbl", { scriptlist = scriptlist } )
		end}
	})
	-- load scriptmanager first
	dcpp._listeners.id[1] = "startup.lua"
	dcpp._listeners[1] =  {
		ownChatOut = function( hub, message )
			if message:sub(1,1) ~= "/" then return nil end
	
			local _,_, cmd, fname = message:find( "([^ ]+)[ ]*(.*)" )
			if cmd == nil then 
				return nil
			elseif cmd == "/lualist" then
				local sc, a = {}, 0
				for i, n in ipairs( scriptlist ) do
					if n[2] == 1 then a = a +1 end
					table.insert( sc, ("[ ".. n[2] .." ] ".. n[1]) )
				end
				hub:addLine( "Scripts: (".. a .."/".. #sc ..")\r\nVersion: ".. _VERSION .."\r\n"..table.concat(sc, "\r\n").."\r\n" )
				return 1	
			elseif cmd == "/luaload" then
				if fname ~= "startup.lua" then
					local ret, err = scriptMan.script:load( DC():FromUtf8(fname) )
					hub:addLine( (err and err or "Lua script '"..fname.."' "..ret) )
				else
					hub:addLine( "ERROR: Use '/luafile startup.lua' if you really want to reload it." )
				end
			    	return 1
			elseif  cmd == "/luareload" then
			    	if scriptMan.script:getid( DC():FromUtf8(fname) ) then
			    		local ret, err = scriptMan.script:load( DC():FromUtf8(fname) )
					hub:addLine( (err and err or "Lua script '"..fname.."' "..ret) )
			    	else
			    		hub:addLine( "ERROR: Lua script '"..fname.."' is not running." )
			    	end
			    	return 1
			elseif cmd == "/luaunload" then
				if fname ~= "startup.lua" then
					local ret = scriptMan.script:unload( DC():FromUtf8(fname) )
					hub:addLine( "Lua script '"..fname.."' "..ret )
				else
					hub:addLine( "ERROR: Use '/luafile startup.lua' if you really want to reload it." )
				end
				return 1
			elseif cmd == "/luaremove" then
				if fname ~= "startup.lua" then
					local ret = scriptMan.script:remove( DC():FromUtf8(fname) )
					hub:addLine( "Lua script '"..fname.."' "..ret )
				else
					hub:addLine( "ERROR: Use '/luafile startup.lua' if you really want to reload it." )
				end
				return 1
			elseif cmd == "/meminfo" then
				collectgarbage("collect")
				local mem = collectgarbage("count")
				local t, i = { "KiB", "MiB", "GiB", "TiB" }, 1
				while mem >= 1024 and i < #t do mem = (mem / 1024); i = i+1 end
				hub:addLine( "Scripts are using ".. string.format("%.2f", mem ) .. " "..t[i].." of memory." )
				return 1
			elseif cmd == "/help" then
				hub:addLine( "\r\n"..string.rep("-", 156).."\r\n	Script Manager\r\n"..string.rep("-", 156).."\r\n"..
					"/lualist					scriptlist\r\n"..
					"/luaload <filename>				load <filename> script\r\n"..
					"/luareload <filename>			reload <filename> script\r\n"..
					"/luaunload <filename>			unload <filename> script\r\n"..
					"/luaremove <filename>			unload and/or remove <filename> from scriptlist\r\n"..
					"/meminfo					memory usage by scripts\r\n"..
					"/luafile startup.lua				reload scripting interface" )
				return nil
			end
		end
	}
	setmetatable( dcpp._listeners[1], { __index = _G } )
	setfenv( dcpp._listeners[1].ownChatOut, dcpp._listeners[1] )
end	

scriptMan.Run = function( this, fname, ... )
	for i in ipairs( dcpp._listeners ) do
		if type( dcpp._listeners[i][fname] ) == "function" then
			local res, ret = pcall( dcpp._listeners[i][fname], ... )
			if not res then
				DC():PrintDebug( " LUA ERROR: ("..dcpp._listeners.id[i]..": "..fname..") "..ret )
			else
				if ret then return 1 end
			end
		end
	end
end

scriptMan.Init()

if not scriptlist then
	local func = loadfile( DC():GetAppPath() .. "scripts\\scriptlist.tbl" )
	if func then
		func()
	else
		local f = io.open( DC():GetAppPath() .. "scripts\\scriptlist.tbl", "w+")
		if f then f:close() end
	end
	
	if type(scriptlist) ~= "table" then
		scriptlist = {}	-- [1] = {scriptname, active (1/0)}
		scriptMan.script:save()
	end
end

dofile = function( filename )
	local fnc, err = loadfile( filename )
	if fnc then 
		setfenv(fnc, getfenv(2))
		fnc()
	else
		DC():PrintDebug( " LUA ERROR: "..err )
	end
end
	
-- _always_ start formatting.lua
scriptMan.script:load( "formatting.lua" )

-- autostart last running scripts but (running) formatting.lua
for i in ipairs( scriptlist ) do
	if scriptlist[i][2] == 1 and scriptlist[i][1] ~= "formatting.lua" then
		local res, err = scriptMan.script:load( scriptlist[i][1] )
		if not res then
			DC():PrintDebug( " "..err)
		end
	end
end

-- run timer
DC():RunTimer(1)

en_dator
Member
Posts: 72
Joined: 01 Apr 2008, 19:24

Re: Startup 2.1.2d

Post by en_dator » 28 Feb 2009, 19:12

aha, so this is how the script manager version looks,
will check this out for sure :)

Toast

Re: Startup 2.1.2d

Post by Toast » 07 Mar 2009, 12:43

another lua file is needed with this startup script if you don't have the file we supply it here for you.

ive tried this version and im very please with it so much better then the old script
Attachments
libsimplepickle.lua
(3.9 KiB) Downloaded 454 times

Yeppy
Newbie
Posts: 4
Joined: 19 Mar 2009, 21:49

Re: Startup 2.1.2d

Post by Yeppy » 20 Mar 2009, 22:02

hmm what does this improve compared to the one coming by default in the clients ? i have rsx so is it useful for me to change to this one ? and why ?

Crise
Senior Member
Posts: 139
Joined: 10 Nov 2007, 21:34

Re: Startup 2.1.2d

Post by Crise » 21 Jul 2009, 21:13

Ok, reposting the cleaned version, this time took the script from the first post (copy paste) and did not make any changes to the code besides white space, I think, since I am no LUA guy it might be safer that way.

And it actually ended becoming much smaller this time around, note that this file uses the UNIX flavor of line ending (ie. LF vs CRLF).

Edit: When I made this I had my tab width set equal to 4 spaces. Also this was made since the codebox on the forums screws up whitespaces.
Attachments
startup.lua
No warranties.
(36.96 KiB) Downloaded 444 times

ATAG
Newbie
Posts: 2
Joined: 12 Oct 2008, 12:23

Re: Startup 2.1.2d

Post by ATAG » 28 Jul 2009, 07:36

In RSX++ we have to use a special formatting.lua file.
CMD Debug Messages window will generate errors without this!

Code: Select all

--[[
-- Commands you want to highlight. Use '.' to mask like: .MSG will catch IMSG and BMSG as well
-- Please follow the table-form
tCommands = {
	[1] = {
    	["cmd"] = "$MyINFO",
		["red"] = 255,
		["green"] = 204,
		["blue"] = 0,
		["isbold"] = true,
		["isitalics"] = false,
		},
	[2] = {
    	["cmd"] = ".INF",
		["red"] = 255,
		["green"] = 204,
		["blue"] = 0,
		["isbold"] = false,
		["isitalics"] = false,
			},
	[3] = {
    	["cmd"] = "$Search",
		["red"] = 255,
		["green"] = 204,
		["blue"] = 0,
		["isbold"] = false,
		["isitalics"] = false,
		},
	[4] = {
    	["cmd"] = ".SCH",
		["red"] = 255,
		["green"] = 204,
		["blue"] = 0,
		["isbold"] = false,
		["isitalics"] = false,
		},
	}
]]

dcpp.OnColorize = function(text)
	-- By Hungarista, 2008.03.20. 16:08:41
	local bFilterKeepAlives = false -- Set to true if you don't want to see the keep-alive messages
                                    --(| on NMDC, empty string on ADC) else set to nil or false
    if bFilterKeepAlives and (data == "|\n" or data == "") then
		-- Uncomment the next line if you want notify about KeepAlives on the statusbar
		--DC():PrintDebug("KeepAlive packet filtered: "..ip:sub(2,-2).."...")
		return 0
	end
	c1 = DC():GetSetting("TextTimestampForeColor")
	b1 = DC():GetSetting("TextTimestampBold")
	i1 = DC():GetSetting("TextTimestampItalic")
	c2 = DC():GetSetting("TextGeneralForeColor")
	b2 = DC():GetSetting("TextGeneralBold")
	i2 = DC():GetSetting("TextGeneralItalic")
	local function iff(c,a,b) return c and a or b end
	local function GetRGB(i)
		return math.fmod(math.fmod(i,65536),256),math.floor(math.fmod(i,65536)/256),math.floor(i/65536)
	end
	text = DC():ToUtf8(text):gsub("([{}\\])","\\%1"):gsub("\r\n","\n"):gsub("\n","\\line\n")
	_,_,timestamp,message = text:find("^(%b[])(.+)$")
	bCommand = false
	-- Colorize the commands - if any
	if type(tCommands) == "table" then
		for i,v in ipairs(tCommands) do
			if message:find("%]\t\t\t"..v["cmd"]) then
				bCommand = true
				message = "\\cf"..(2+i)..iff(v.isbold,"\\b","")..iff(v.isitalics,"\\i","").." "..message..
	            iff(v.isbold,"\\b0","")..iff(v.isitalics,"\\i0","")
				break
			end
		end
	end
	if bCommand then
		text = iff(b1==1,"\\b","")..iff(i1==1,"\\i","").."\\cf1 "..timestamp..iff(b1==1,"\\b0","")..iff(i1==1,"\\i0","")..message
	else
		text = iff(b1==1,"\\b","")..iff(i1==1,"\\i","").."\\cf1 "..timestamp..iff(b1==1,"\\b0","")..iff(i1==1,"\\i0","")..
		iff(b2==1,"\\b","")..iff(i2==1,"\\i","").."\\cf2 "..message..iff(b2==1,"\\b0","")..iff(i2==1,"\\i0","")
	end
	if text:sub(-6,-1) ~= "\\line\n" then text = text.."\\line\n" end
	r1,g1,b1 = GetRGB(c1)
	r2,g2,b2 = GetRGB(c2)
	local ret = "{\\urtf1\\ansi\\ansicpg1252\\deff0\\plain0\n"..
			  "{\\colortbl ;"..
			  "\\red"..r1.."\\green"..g1.."\\blue"..b1..";"..
			  "\\red"..r2.."\\green"..g2.."\\blue"..b2..";"
			  if type(tCommands) == "table" then
				for i,v in ipairs(tCommands) do
					ret = ret.."\\red"..v["red"].."\\green"..v["green"].."\\blue"..v["blue"]..";"
				end
			  end
	return ret.."}\n\\cf1 "..text.."}\n"
end

Toast

Re: Startup 2.1.2d

Post by Toast » 28 Jul 2009, 08:20

Nice all though that will only work until Adrian changes to the new lua api

adrian_007
Senior Member
Posts: 126
Joined: 06 Jan 2008, 13:00

Re: Startup 2.1.2d

Post by adrian_007 » 28 Jul 2009, 14:01

in 1.1 cdm formatting is removed, in future will be hardcoded.

s3m0
Newbie
Posts: 1
Joined: 02 Jul 2010, 22:08

Re: Startup 2.1.2d

Post by s3m0 » 02 Jul 2010, 22:18

Thanks.

Just want to mention that I'm using BCDC++ on win7 and my scripts are not in the DC():GetAppPath().."scripts\\script.lua" folder, but rather in DC():GetConfigPath().."scripts\\script.lua". Had to replace GetAppPath with GetConfigPath for startup.lua to work properly. I'm sure that there could be a better solution, for an example to make it work in both cases, but I'm not that much into scripting yet to be able to make it. :D

Locked