Skip to content

Commit 2edc275

Browse files
committed
feat: basic twitch integration
Stolen wholesale from Krzyhau/KrzyMod There seem to be some threading concerns but this seems to work well enough Co-authored by: Krzyhau <krzyht@gmail.com>
1 parent 02096eb commit 2edc275

File tree

5 files changed

+242
-0
lines changed

5 files changed

+242
-0
lines changed

src/Features/TwitchIntegration.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "Event.hpp"
2+
#include "Modules/Client.hpp"
3+
#include "Utils/TwitchConnection.hpp"
4+
#include "Variable.hpp"
5+
6+
Variable sar_twitch_chat_enabled("sar_twitch_chat_enabled", "0", "Enables Twitch chat integration.\n");
7+
Variable sar_twitch_chat_channel("sar_twitch_chat_channel", "", "The Twitch channel to connect to.\n");
8+
Variable sar_twitch_chat_color("sar_twitch_chat_color", "255 255 255", "The color of the Twitch chat messages.\n");
9+
10+
TwitchConnection twitchConnection;
11+
12+
ON_EVENT(PRE_TICK) {
13+
if (!sar_twitch_chat_enabled.GetBool() || strlen(sar_twitch_chat_channel.GetString()) == 0) {
14+
if (twitchConnection.IsConnected()) {
15+
twitchConnection.Disconnect();
16+
}
17+
return;
18+
}
19+
std::string channel = sar_twitch_chat_channel.GetString();
20+
if (twitchConnection.GetChannel().compare(channel) != 0) {
21+
twitchConnection.JoinChannel(channel);
22+
}
23+
if (!twitchConnection.IsConnected()) {
24+
twitchConnection.JoinChannel(channel);
25+
}
26+
auto twitchMsgs = twitchConnection.FetchNewMessages();
27+
for (auto msg : twitchMsgs) {
28+
std::string message = msg.username + ": " + msg.message;
29+
Color color = Utils::GetColor(sar_twitch_chat_color.GetString()).value_or(Color(255, 255, 255));
30+
client->Chat(color, message.c_str());
31+
}
32+
}
33+
34+
ON_EVENT(SAR_UNLOAD) {
35+
if (twitchConnection.IsConnected()) {
36+
twitchConnection.Disconnect();
37+
}
38+
}

src/Utils/NetworkConnection.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#include "NetworkConnection.hpp"
2+
3+
#include <iostream>
4+
5+
#ifdef _WIN32
6+
# include <winsock2.h>
7+
# include <ws2tcpip.h>
8+
#else
9+
# include <cstring>
10+
# include <netdb.h>
11+
# include <sys/socket.h>
12+
# include <sys/types.h>
13+
# include <unistd.h>
14+
# define INVALID_SOCKET -1
15+
# define SOCKET_ERROR -1
16+
#endif
17+
18+
NetworkConnection::NetworkConnection(std::string ip, int port) {
19+
ChangeAddress(ip, port);
20+
}
21+
22+
void NetworkConnection::ChangeAddress(std::string ip, int port) {
23+
if (IsConnected()) Disconnect();
24+
this->ip = ip;
25+
this->port = port;
26+
}
27+
28+
bool NetworkConnection::Connect() {
29+
Disconnect();
30+
31+
#ifdef WIN32
32+
WSADATA wsa;
33+
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) return false;
34+
#endif
35+
36+
if ((socketID = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return false;
37+
38+
addrinfo hints, *res;
39+
memset(&hints, 0, sizeof(hints));
40+
hints.ai_socktype = SOCK_STREAM;
41+
hints.ai_family = AF_INET;
42+
if ((getaddrinfo(ip.c_str(), NULL, &hints, &res)) != 0) return false;
43+
sockaddr_in addr;
44+
addr.sin_addr = ((sockaddr_in *)res->ai_addr)->sin_addr;
45+
addr.sin_family = AF_INET;
46+
addr.sin_port = htons(port);
47+
freeaddrinfo(res);
48+
49+
if (connect(socketID, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) return false;
50+
51+
connected = true;
52+
connThread = std::thread([&]() {
53+
while (IsConnected()) {
54+
while (dataReady);
55+
56+
// checking if there is a message to receive
57+
fd_set fdset{1, {socketID}};
58+
timeval time{0, 10000};
59+
int selectResult = select(socketID + 1, &fdset, NULL, NULL, &time);
60+
if (selectResult < 0) break;
61+
if (selectResult == 0) continue;
62+
63+
// receiving a message
64+
memset(&sockbuff, '\0', sizeof(sockbuff));
65+
bufflen = recv(socketID, sockbuff, sizeof(sockbuff) - 1, 0);
66+
if (bufflen <= 0) break;
67+
68+
dataReady = true;
69+
}
70+
connected = false;
71+
});
72+
73+
return true;
74+
}
75+
76+
void NetworkConnection::Disconnect() {
77+
connected = false;
78+
dataReady = false;
79+
80+
if (socketID != 0) {
81+
shutdown(socketID, 2); // SHUT_RDWR on Linux, SD_BOTH on Windows
82+
83+
connThread.join();
84+
#ifdef WIN32
85+
closesocket(socketID);
86+
WSACleanup();
87+
#else
88+
close(socketID);
89+
#endif
90+
91+
socketID = 0;
92+
}
93+
}
94+
95+
bool NetworkConnection::TryProcessData(std::function<void(char *, int)> func) {
96+
if (!dataReady) return false;
97+
func(sockbuff, bufflen);
98+
dataReady = false;
99+
return true;
100+
}
101+
102+
void NetworkConnection::SendData(char *data, int size) {
103+
send(socketID, data, size, 0);
104+
}
105+
106+
void NetworkConnection::SendData(std::string data) {
107+
SendData((char*)data.c_str(), data.size());
108+
}

src/Utils/NetworkConnection.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <thread>
5+
#include <vector>
6+
#include <functional>
7+
8+
class NetworkConnection {
9+
private:
10+
int socketID = 0;
11+
std::string ip;
12+
int port;
13+
bool dataReady = false;
14+
bool connected = false;
15+
std::thread connThread;
16+
17+
int bufflen = 0;
18+
char sockbuff[16384];
19+
public:
20+
NetworkConnection(std::string ip, int port);
21+
void ChangeAddress(std::string ip, int port);
22+
bool Connect();
23+
void Disconnect();
24+
bool TryProcessData(std::function<void(char *, int)> func);
25+
void SendData(char *data, int size);
26+
void SendData(std::string data);
27+
bool IsConnected() { return connected; }
28+
};

src/Utils/TwitchConnection.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "TwitchConnection.hpp"
2+
3+
#include <cstring>
4+
5+
TwitchConnection::TwitchConnection()
6+
: NetworkConnection("irc.chat.twitch.tv", 6667) {
7+
8+
}
9+
10+
void TwitchConnection::JoinChannel(std::string name) {
11+
channel = name;
12+
13+
Connect();
14+
15+
SendData("NICK justinfan8\nJOIN #" + channel + "\n");
16+
}
17+
18+
std::vector<TwitchConnection::Message> TwitchConnection::FetchNewMessages() {
19+
20+
std::vector<TwitchConnection::Message> messages;
21+
22+
TryProcessData([&](char *sockbuff, int messageLen) {
23+
char *startChar = sockbuff;
24+
25+
for (char *c = sockbuff; *c != '\0'; c++) {
26+
if (*c == '\n') {
27+
int len = (c - startChar);
28+
std::string message(startChar, len);
29+
30+
if (message[0] == 'P') {
31+
SendData("PONG :tmi.twitch.tv\n");
32+
} else if (message[0] == ':' && std::strstr(message.c_str(), "PRIVMSG")) {
33+
std::string nickname(startChar + 1, message.find('!') - 1);
34+
std::string msg(message.begin() + message.find(':', 1) + 1, message.end());
35+
messages.push_back({nickname, msg});
36+
}
37+
38+
startChar += len + 1;
39+
}
40+
}
41+
});
42+
43+
return messages;
44+
}

src/Utils/TwitchConnection.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "NetworkConnection.hpp"
2+
3+
#pragma once
4+
class TwitchConnection : protected NetworkConnection{
5+
public:
6+
struct Message{
7+
std::string username;
8+
std::string message;
9+
};
10+
private:
11+
std::string channel;
12+
13+
public:
14+
TwitchConnection();
15+
void JoinChannel(std::string name);
16+
std::string GetChannel() { return channel; }
17+
std::vector<Message> FetchNewMessages();
18+
19+
using NetworkConnection::Connect;
20+
using NetworkConnection::Disconnect;
21+
using NetworkConnection::IsConnected;
22+
using NetworkConnection::SendData;
23+
};
24+

0 commit comments

Comments
 (0)