/* command-line twitch chat demo for Barony devs
*
* (C)2021 Pegasus Epsilon - Distribute Unmodified
* https://pegasus.pimpninjas.org/license
*
*/
#define _POSIX_C_SOURCE 200112L
// _POSIX_C_SOURCE 200112 is needed for...
// struct addrinfo, AI_PASSIVE, getaddrinfo(), gai_strerror(), freeaddrinfo(),
// fdopen()
#include <netdb.h>
// struct addrinfo, AI_PASSIVE, getaddrinfo(), gai_strerror(), freeaddrinfo()
// AF_UNSPEC, PF_UNSPEC, SOCK_STREAM, socket(), connect()
#include <sys/select.h> // select()
#include <unistd.h> // close()
#include <fcntl.h> // fcntl(), F_GETFL, F_SETFL, O_NONBLOCK
#include <stdlib.h> // malloc(), free()
#include <stdbool.h> // bool, true, false
#include <stdio.h>
// printf(), FILE, fdopen(), puts(), fopen(),
// fread(), fclose(), fputs(), fgets()
#include <string.h> // strchr()
#define IRC_NICK "pegasus_epsilon"
#define IRC_OAUTH "/home/pegasus/private_html/twitch.oauth"
struct addrinfo *resolve (char *host, char *svc) {
struct addrinfo *results, hints = { 0 };
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = PF_UNSPEC;
int s;
if ((s = getaddrinfo(host, svc, &hints, &results)))
printf("getaddrinfo: %s\n", gai_strerror(s));
return results;
}
bool nonblock (int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (-1 == flags) {
perror("fcntl");
return false;
}
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
return true;
}
FILE *connect_nonblock (char *host, char *svc) {
struct addrinfo *addr = resolve(host, svc);
for (struct addrinfo *rp = addr; rp; rp = rp->ai_next) {
int sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (0 > sock) continue;
if (
connect(sock, rp->ai_addr, sizeof(*rp->ai_addr))
|| !nonblock(sock)
) {
close(sock);
continue;
}
freeaddrinfo(addr);
return fdopen(sock, "r+");
}
puts("connect failure");
return NULL;
}
char *read_bytes_from_file (char *file, size_t len) {
char *out = malloc(sizeof(char) * len);
FILE *in = fopen(file, "r");
fread(out, len, 1, in);
fclose(in);
return out;
}
void send_bytes_from_file (char *in, size_t howmuch, FILE *out) {
char *str = read_bytes_from_file(in, howmuch);
fwrite(str, howmuch, 1, out);
free(str);
}
int main (void) {
FILE *irc = connect_nonblock("irc.chat.twitch.tv", "6667");
if (!irc) return EXIT_FAILURE;
fputs("PASS oauth:", irc);
send_bytes_from_file(IRC_OAUTH, 30, irc);
fputs("\r\n", irc);
fputs("NICK " IRC_NICK "\r\n", irc);
fputs("CAP REQ :twitch.tv/membership\r\n", irc);
fputs("JOIN #" IRC_NICK "\r\n", irc);
fflush(irc);
fd_set listen, readable;
FD_ZERO(&listen);
FD_SET(fileno(irc), &listen);
FD_SET(fileno(stdin), &listen);
for (
readable = listen;
select(FD_SETSIZE, &readable, NULL, NULL, NULL);
readable = listen
) {
char buf[1024];
if (FD_ISSET(fileno(irc), &readable)) {
while (fgets(buf, sizeof(buf), irc)) {
char *p = strchr(buf, '!');
char *nick, *type, *data;
if (p && p < strchr(buf, ' ')) {
*p++ = 0;
nick = buf + 1;
*(p = strchr(p, ' ')) = 0;
type = ++p;
*(p = strchr(p, ' ')) = 0;
if (!strcmp(type, "PRIVMSG")) {
*(p = strchr(++p, ' ')) = 0;
if (':' == *++p) {
data = p + 1;
printf("<%s> %s", nick, data);
}
}
}
}
sleep(1);
}
if (FD_ISSET(fileno(stdin), &readable)) {
fgets(buf, sizeof(buf), stdin);
fprintf(irc, "PRIVMSG #" IRC_NICK " :%s\r\n", buf);
fflush(irc);
printf("sent> %s", buf);
buf[0] = 0;
}
}
return 0;
}