Created
June 15, 2020 07:26
-
-
Save notanimposter/1e5a2375fa1c3e1df037ccec255753f4 to your computer and use it in GitHub Desktop.
A work-in-progress IRC/Twitch chat library in Vala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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