This turned out to be a bigger problem than I envisioned and every time I tried something new, I kept hitting a brick wall. Suffice it to say, I ended up having to resort to old school tailing a logfile to get it working. Not my best work, but it does what I wanted. Of course, this method could cause a slight delay on startup if your eqlog file is not trimmed as it has to parse the last 300 lines of the log in order to work and depending on the size of the file, that can take a minute. I would suggest rotating/deleting your eqlog files on a semi-regular basis.
[CODE lang="Lua" title="LeaveChannel.Lua"]-----------------------------------
-- name: LeaveChannel
-- version: 1.5
-- Tail the EverQuest eqlog file and auto-leave channels when they appear in the log.
-----------------------------------
local mq = require("mq")
-- ===== CONFIG =====
-- Path to your eqlog file. Set this to your actual file.
-- Example windows path (use double backslashes) :
-- eqlogPath = "C:\\Users\\JoeCool\\Documents\\EverQuest\\eqlog_JoeCool_bristle.txt"
local eqlogPath = "" -- <-- PUT YOUR PATH HERE
-- Channels you want to automatically leave when detected in the log
local channelsToLeave = {"NewPlayers"}
local debug = true -- set false to silence debug /echos
local startup_scan_lines = 300 -- how many lines to scan on startup
local tail_sleep_ms = 200 -- how long to wait when at EOF
local leave_debounce_sec = 3 -- do not attempt to leave same channel more than once in this many seconds
-- ===== internal state =====
local lastLeaveTimes = {} -- [channel] = os.time()
-- debug helper
local function debugEcho(fmt, ...)
if not debug then return end
if select('#', ...) > 0 then
mq.cmdf('/echo [LeaveChannelLog] ' .. fmt, ...)
else
mq.cmdf('/echo [LeaveChannelLog] %s', fmt)
end
end
-- check file exists
local function file_exists(path)
local f = io.open(path, "r")
if f then f:close(); return true end
return false
end
-- read last N lines from file (simple circular buffer)
local function read_last_lines(path, maxlines)
local t = {}
local f, err = io.open(path, "r")
if not f then return t end
for line in f:lines() do
t[#t+1] = line
if #t > maxlines then table.remove(t, 1) end
end
f:close()
return t
end
-- open file for tailing and seek to end
local function open_tail(path)
local f, err = io.open(path, "r")
if not f then return nil, err end
-- move to EOF
f:seek("end")
return f
end
local function canLeaveNow(channel)
local now = os.time()
local last = lastLeaveTimes[channel] or 0
if now - last < leave_debounce_sec then
return false
end
lastLeaveTimes[channel] = now
return true
end
local function doLeave(channel)
if not canLeaveNow(channel) then
debugEcho("Debounced leave for %s (recently left)", channel)
return
end
debugEcho("Leaving channel: %s", channel)
mq.cmdf("/leave %s", channel) -- /leave and /chat leave are equivalent for you
mq.cmdf("/echo Left channel: %s", channel)
end
-- process a single log line and look for channel join/listing lines
local function process_line(line)
if not line then return end
local l = string.lower(line)
-- match 'Channels:' lines (example user provided: "Channels: 1=NewPlayers")
if string.find(l, "channels:") then
for _, ch in ipairs(channelsToLeave) do
if string.find(l, string.lower(ch), 1, true) then
debugEcho("Detected channel token in 'Channels:' line: %s", line)
doLeave(ch)
end
end
return
end
-- also match lines like "Channel NewPlayers(195) members:" which you said appears in /list
for _, ch in ipairs(channelsToLeave) do
local pattern = "channel%s+" .. string.lower(ch) -- "channel newplayers"
if string.find(l, pattern) then
debugEcho("Detected channel listing line: %s", line)
doLeave(ch)
return
end
end
end
-- startup scan: read last lines and process them (covers cases where you already joined before script start)
local function startup_scan()
if not file_exists(eqlogPath) then
debugEcho("eqlog file not found at '%s'. Please set eqlogPath in the script.", tostring(eqlogPath))
return
end
debugEcho("Performing startup scan (last %d lines) of %s", startup_scan_lines, eqlogPath)
local lines = read_last_lines(eqlogPath, startup_scan_lines)
for _, line in ipairs(lines) do
process_line(line)
end
end
-- tail loop: continuously read appended lines
local function tail_loop()
local f, err = open_tail(eqlogPath)
if not f then
debugEcho("Failed to open eqlog for tailing: %s", tostring(err))
return
end
debugEcho("Tailing eqlog: %s", eqlogPath)
while true do
local line = f:read("*l")
if line then
process_line(line)
else
-- at EOF: detect truncation/rotation
local ok, curPos = pcall(function() return f:seek() end)
if not ok or not curPos then
-- file handle probably invalid; attempt reopen
debugEcho("File handle invalid; reopening log")
f:close()
mq.delay(500)
f, err = open_tail(eqlogPath)
if not f then
debugEcho("Reopen failed: %s; will retry", tostring(err))
mq.delay(1000)
end
else
-- get end pos
local ok2, endPos = pcall(function() return f:seek("end") end)
if ok2 and endPos and endPos < curPos then
-- truncated/rotated
debugEcho("Log truncated/rotated; reopening")
f:close()
mq.delay(500)
f, err = open_tail(eqlogPath)
if not f then
debugEcho("Reopen failed: %s; will retry", tostring(err))
mq.delay(1000)
end
else
-- restore pointer
pcall(function() f:seek("set", curPos) end)
mq.delay(tail_sleep_ms)
end
end
end
end
end
-- ===== main =====
if not eqlogPath or eqlogPath == "" then
debugEcho("eqlogPath is empty. Edit the script and set eqlogPath to your eqlog_<char>_<server>.txt file.")
return
end
-- startup: scan, then start background tailing loop
startup_scan()
-- run tail_loop in the main loop so MQ yields and you can still stop script
local ok, err = pcall(function() tail_loop() end)
if not ok then
debugEcho("Tail loop exited with error: %s", tostring(err))
end
-- fallback keepalive (should never reach here)
while true do
mq.delay(1000)
end
[/CODE]