A walkthrough of leveraging Twitch's IRC protocol to build a custom, headless auto-greeting bot in Ruby, evolving from a HexChat plugin to a standalone app.
During a period of Twitch platform instability, I discovered that its chat system is built on the Internet Relay Chat (IRC) protocol. This realization opened a door for automation. By connecting directly to Twitch's IRC servers, I could create a bot to interact with chat channels, independent of the web interface.
My initial goal was simple: automatically greet new users who joined a chat. This post details the journey from a simple client-side plugin to a robust, headless Ruby application running on a Raspberry Pi.
Connecting to Twitch IRC is straightforward. The official documentation provides the necessary details:
irc.chat.twitch.tv
6697
(for SSL)Using a standard IRC client like HexChat, I could join any channel (e.g., #channelname
) and see raw IRC events, including user JOIN
messages, which are normally hidden in the standard Twitch UI.
HexChat supports scripting in Lua, which was perfect for a quick prototype. The API provides a hexchat.hook_print("Join", on_join)
function, which allows you to execute a callback whenever a "Join" event occurs.
My first script had two key functions:
on_join(info)
: This function would trigger when a user joined, extract their nickname, and send a greeting message to the channel.channel_username.mem
for each user greeted.Here is a snippet of the initial Lua proof of concept:
hexchat.register("auto-greeter", "0.1", "Welcomes new members")
-- A simple file-based check to see if we've met the user before
local function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
local function on_join(info)
local nick = info[1]
local chan = info[2]
local memory_file = "C:\\Path\\To\\HexChat\\" .. chan .. "_" .. nick .. ".mem"
if file_exists(memory_file) == false then
-- Create the memory file and send a greeting
touch_file(memory_file)
hexchat.command("MSG " .. chan .. " " .. "Welcome, " .. nick .. "!")
end
return hexchat.EAT_NONE
end
hexchat.hook_print("Join", on_join)
This worked, but it had limitations. It was dependent on the HexChat client, and Lua's limited file system support made more complex logic difficult. It was time to build a proper, standalone application.
I chose Ruby for its strong networking libraries and my familiarity with the language. The goal was to create a "headless" bot that could run on a server (like a Raspberry Pi) without a GUI.
This approach offered several advantages:
The Ruby script connects to the Twitch IRC server using a standard TCP socket. It handles the initial authentication (PASS
and NICK
commands) and then enters a loop to read messages from the server.
Moving to a more powerful language allowed for significant improvements. Based on feedback that greeting "lurkers" (users who don't chat) could be intrusive, I changed the core logic.
JOIN
event, the bot now listens for PRIVMSG
(a user message). It only greets a user after they've sent their first message in a session.The core of the Ruby bot is built using the Cinch IRC library, which simplifies connecting to the server and handling events. The main logic is contained within a run_bot.rb
script.
require 'cinch'
require_relative 'lib/helpers.rb'
# --- Configuration ---
username = get_arg(0) # Bot's Twitch username
password = get_arg(1) # Bot's OAuth token
input_channels = read_config("./channels.txt")
input_greetings = read_config("./greetings.txt")
input_return_greetings = read_config("./return_greetings.txt")
# --- Bot Setup ---
bot = Cinch::Bot.new do
configure do |c|
c.server = "irc.chat.twitch.tv"
c.port = 6697
c.ssl.use = true
c.nick = username
c.password = "oauth:#{password}"
c.channels = input_channels
end
# Request Twitch-specific capabilities
on :connect do
bot.irc.send("CAP REQ :twitch.tv/membership")
end
# --- Main Logic: Greeting Users ---
on :message do |m|
# Don't greet self or the channel bot
next if m.user.nick == bot.nick || m.user.nick == m.channel.name[1..-1]
memory_file = "met/#{m.channel.name}/#{m.user.nick}.mem"
now = Time.now
# Check if we've greeted this user recently
if File.exist?(memory_file)
last_greeted_time = File.mtime(memory_file)
# Only greet if it's been more than 12 hours
if now > last_greeted_time + (12 * 60 * 60)
m.reply "Welcome back, #{m.user.nick}! #{input_return_greetings.sample}"
FileUtils.touch(memory_file) # Update timestamp
end
else
# First time greeting, create the memory file
FileUtils.mkdir_p(File.dirname(memory_file))
FileUtils.touch(memory_file)
m.reply "Welcome, #{m.user.nick}! #{input_greetings.sample}"
end
end
end
bot.start
This project was a fantastic exercise in practical automation. It started with simple curiosity and evolved from a basic client plugin into a standalone, headless application. By moving to Ruby, I gained greater control and flexibility, allowing for more nuanced and respectful bot behavior. The final bot now runs quietly on a Raspberry Pi, a testament to the power of understanding the protocols that underpin the web services we use every day.
You can find the source code for the Ruby bot on my GitHub.