A work-in-progress IRC/Twitch chat library in Vala
namespace TwitchChat { | |
public class Chat { | |
public string name; | |
public string streamer { | |
owned get { | |
return name.substring(1); | |
} | |
} | |
public string[] users; | |
public bool justNamesed = false; | |
public Chat(string name) { | |
this.name = name; | |
users = new string[0]; | |
} | |
} | |
public bool isMod(string tags) { | |
switch (IRCConnection.searchTag(tags, "user-type")) { | |
case "mod": | |
case "admin": | |
case "global_mod": | |
return true; | |
} | |
var badges = IRCConnection.searchTag(tags, "badges"); | |
if (badges.index_of("broadcaster") >= 0) return true; | |
return false; | |
} | |
public class IRCConnection { | |
public bool printThings = true; | |
private void log(string whatever) { | |
if (printThings) print(whatever); | |
} | |
public signal void onDoneConnecting(); | |
public signal void onMessage(Chat where, string who, string what, string tags); | |
public signal void onMessageSelf(Chat where, string who, string what, string tags); | |
public signal void onAction(Chat where, string who, string what, string tags); | |
public signal void onNotice(string notice); | |
public signal void onError(string error); | |
public signal void onDoneJoining(Chat where); | |
public signal void onUserJoin(Chat where, string who); | |
public signal void onUserPart(Chat where, string who); | |
public signal void onUserQuit(Chat where, string who, string what); | |
public signal void onDoneNamesing(Chat where); | |
//onMode | |
public signal void onTwitchNotice(Chat where, string notice, string tags); | |
public signal void onUserBanned(Chat where, string who, string tags); | |
public signal void onRoomState(Chat where, string tags); | |
public signal void onUserState(Chat where, string who, string tags); | |
public signal void onUserSubbed(Chat where, string who, string what, string tags); | |
public List<Chat> chats; | |
public string username; | |
string password; | |
GLib.SocketConnection connection; | |
GLib.DataInputStream response; | |
public bool connected; | |
Queue<string> toSend; | |
private Chat getChatByName(string name) { | |
foreach (Chat c in chats) { | |
if (c.name == name) return c; | |
} | |
error("fix this later"); | |
} | |
public static string searchTag(string tags, string tagName) { | |
try { | |
MatchInfo match; | |
if (new Regex(tagName+"=([^;\\s]*)").match(tags, 0, out match)) | |
return match.fetch(1); | |
} catch (RegexError e) {} //my regex is fine, thanks | |
return ""; | |
} | |
public IRCConnection(string username, string password) { | |
this.username = username; | |
this.password = password; | |
chats = new List<Chat>(); | |
connected = false; | |
toSend = new Queue<string>(); | |
onDoneConnecting.connect(() => { | |
string cmd; | |
while ((cmd = toSend.pop_head()) != null) sendRaw(cmd); | |
cap_req("twitch.tv/membership"); | |
cap_req("twitch.tv/tags"); | |
cap_req("twitch.tv/commands"); | |
}); | |
} | |
~IRCConnection() { | |
log("Exiting\n"); | |
quit(); | |
} | |
public async void connect() { | |
try { | |
var resolver = Resolver.get_default(); | |
log("Resolving irc.chat.twitch.tv...\n"); | |
var addresses = yield resolver.lookup_by_name_async("irc.chat.twitch.tv", null); | |
var address = addresses.nth_data(0); | |
log("Resolved irc.chat.twitch.tv to %s\n".printf(address.to_string())); | |
var client = new SocketClient(); | |
connection = yield client.connect_async(new InetSocketAddress(address, 6667)); | |
log("Connected to port 6667\n"); | |
this.connected = true; | |
response = new DataInputStream(connection.input_stream); | |
sendRaw("PASS %s".printf(password)); | |
sendRaw("NICK %s".printf(username)); | |
log("Logging in...\n"); | |
wait.begin(); | |
} catch (Error e) { | |
error("Could not connect: %s".printf(e.message)); | |
} | |
} | |
public async void wait() { | |
print(""); //necessary. don't ask. | |
try { | |
var line = yield response.read_line_async(); | |
if (line != null) parseLine(line); | |
} catch (Error e) { | |
log("Error waiting: %s\n".printf(e.message)); | |
} | |
if (connected) wait.begin(); | |
} | |
private void parseLine(string input) { | |
log("RECV %s\n".printf(input)); | |
string line = input; | |
string tags = ""; | |
if (line[0] == '@') { | |
tags = line.split(" ")[0]; | |
line = line.substring(line.index_of(" ")+1); | |
} | |
if (line[0] != ':') { | |
var cmd = line.split(" ")[0].strip(); | |
var msg = line.split(" :")[1].strip(); | |
switch(cmd.up()) { | |
case "PING": | |
sendRaw(line.strip().replace("PING","PONG")); | |
break; | |
case "NOTICE": | |
onNotice(msg); | |
break; | |
case "ERROR": | |
onError(msg); | |
break; | |
default: | |
log("Unhandled server command: %s\n".printf(line)); | |
break; | |
} | |
return; | |
} | |
if ((line.split(" ")[1][0]).isdigit()) { | |
var tokens = line.split(" "); | |
var cmd = tokens[1].strip(); | |
var msg = line.split(" :")[1].strip(); | |
switch(cmd) { | |
case "001": | |
onDoneConnecting(); | |
break; | |
case "002": | |
case "003": | |
case "004": | |
case "375": | |
case "372": | |
break; | |
case "376": | |
//done logging in | |
break; | |
case "353": | |
//multiple? | |
var c = getChatByName(tokens[4]); | |
c.users = msg.split(" "); | |
c.justNamesed = true; | |
break; | |
case "366": | |
foreach (Chat c in chats) { | |
if (c.justNamesed) { | |
c.justNamesed = false; | |
onDoneNamesing(c); //c.users); //maybe fix it so it's copying the array | |
} | |
} | |
break; | |
default: | |
log("Unhandled numeric command: %s\n".printf(line)); | |
break; | |
} | |
} else { | |
var csep = line.split(":"); | |
var info = csep[1]; | |
var msg = line.split(" :")[1]; | |
var tokens = info.split(" "); | |
var cmd = tokens[1].strip(); | |
var channel = tokens[2].strip(); | |
var who = tokens[0].strip().split("!")[0]; | |
switch (cmd.up()) { | |
case "PRIVMSG": | |
if (who == username) { | |
onMessageSelf(getChatByName(channel), who, msg, tags); | |
break; | |
} | |
if (msg.split(" ")[0] == "\001ACTION") { | |
msg = msg.replace("\001ACTION ", ""); | |
onAction(getChatByName(channel), who, msg, tags); | |
} else onMessage(getChatByName(channel), who, msg, tags); | |
break; | |
case "QUIT": | |
onUserQuit(getChatByName(channel), who, msg); | |
break; | |
case "PART": | |
onUserPart(getChatByName(channel), who); | |
break; | |
case "JOIN": | |
if (who == username) { | |
chats.append(new Chat(channel)); | |
onDoneJoining(getChatByName(channel)); | |
} else onUserJoin(getChatByName(channel),who); | |
break; | |
case "USERNOTICE": | |
onUserSubbed(getChatByName(channel), who, msg, tags); | |
break; | |
case "NOTICE": | |
onTwitchNotice(getChatByName(channel), msg, tags); | |
break; | |
case "ROOMSTATE": | |
onRoomState(getChatByName(channel), tags); | |
break; | |
case "USERSTATE": | |
onUserState(getChatByName(channel), who, tags); | |
break; | |
case "CLEARCHAT": | |
break; | |
case "MODE": | |
break; | |
case "CAP": | |
break; | |
default: | |
log("Unhandled named command: %s\n".printf(line)); | |
break; | |
} | |
} | |
} | |
private void sendRaw(string line) { | |
log("SEND %s\n".printf(line)); | |
try { | |
connection.output_stream.write((line+"\r\n").data); | |
connection.output_stream.flush(); | |
} catch (Error e) { | |
error("sendRaw: %s".printf(e.message)); | |
} | |
} | |
public void send(string cmd) { | |
if (connected) sendRaw(cmd); | |
else toSend.push_tail(cmd); | |
} | |
public void join(string where) { | |
send("JOIN %s".printf(where)); | |
} | |
public void part(string where) { | |
send("PART %s".printf(where)); | |
} | |
public void quit() { | |
send("QUIT :bye"); | |
connected = false; | |
} | |
public void privmsg(string where, string what) { | |
send("PRIVMSG %s :%s".printf(where, what)); | |
} | |
public void names(string where) { | |
send("NAMES %s".printf(where)); | |
} | |
public void cap_req(string what) { | |
send("CAP REQ :%s".printf(what)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment