<commit_before>#include <cpr/cpr.h>
#include <fstream>
#include <regex>
#include <tw/reader.h>
#include <utils.h>
#include "permissions.h"
#include "TwitchBot.h"
#include "version.h"

static const char *TWITCH_SERV = "irc.twitch.tv";
static const char *TWITCH_PORT = "80";

static std::string urltitle(const std::string &resp);

TwitchBot::TwitchBot(const std::string &nick, const std::string &channel,
		const std::string &password, const std::string &token,
		ConfigReader *cfgr)
	: m_connected(false), m_nick(nick), m_channelName(channel),
	m_token(token), m_client(TWITCH_SERV, TWITCH_PORT),
	m_cmdHandler(nick, channel.substr(1), token, &m_mod, &m_parser,
			&m_event, &m_giveaway, cfgr), m_cfgr(cfgr),
	m_event(cfgr), m_giveaway(channel.substr(1), time(nullptr), cfgr),
	m_mod(&m_parser, cfgr)
{
	if ((m_connected = m_client.cconnect())) {
		/* send required IRC data: PASS, NICK, USER */
		sendData("PASS " + password);
		sendData("NICK " + nick);
		sendData("USER " + nick);

		/* enable tags in PRIVMSGs */
		sendData("CAP REQ :twitch.tv/tags");

		/* join channel */
		sendData("JOIN " + channel);

		m_tick = std::thread(&TwitchBot::tick, this);

		/* create giveaway checking event */
		m_event.add("checkgiveaway", 10, time(nullptr));

		/* read the subscriber messages */
		parseSubMsg(m_subMsg, "submessage");
		parseSubMsg(m_resubMsg, "resubmessage");
	}
}

TwitchBot::~TwitchBot()
{
	m_client.cdisconnect();
}

bool TwitchBot::isConnected() const
{
	return m_connected;
}

/* disconnect: disconnect from Twitch server */
void TwitchBot::disconnect()
{
	m_client.cdisconnect();
	m_connected = false;
	m_tick.join();
}

/* serverLoop: continously receive and process data */
void TwitchBot::serverLoop()
{
	std::string msg;
	/* continously receive data from server */
	while (true) {
		if (m_client.cread(msg) <= 0) {
			std::cerr << "No data received. Exiting." << std::endl;
			disconnect();
			break;
		}
		std::cout << "[RECV] " << msg << std::endl;
		processData(msg);
		if (!m_connected)
			break;
	}
}

/* sendData: format data and write to client */
bool TwitchBot::sendData(const std::string &data)
{
	/* format string by adding CRLF */
	std::string formatted = data
		+ (utils::endsWith(data, "\r\n") ? "" : "\r\n");
	/* send formatted data */
	int32_t bytes = m_client.cwrite(formatted);
	std::cout << (bytes > 0 ? "[SENT] " : "Failed to send: ")
		<< formatted << std::endl;

	/* return true iff data was sent succesfully */
	return bytes > 0;
}

/* sendMsg: send a PRIVMSG to the connected channel */
bool TwitchBot::sendMsg(const std::string &msg)
{
	return sendData("PRIVMSG " + m_channelName + " :" + msg);
}

/* sendPong: send an IRC PONG */
bool TwitchBot::sendPong(const std::string &ping)
{
	/* first six chars are "PING :", server name starts after */
	return sendData("PONG " + ping.substr(6));
}

/* processData: send data to designated function */
void TwitchBot::processData(const std::string &data)
{
	if (data.find("Error logging in") != std::string::npos
			|| data.find("Login unsuccessful")
			!= std::string::npos) {
		disconnect();
		std::cerr << "\nCould not log in to Twitch IRC.\nMake sure "
			<< utils::configdir() << utils::config("config")
			<< " is configured correctly." << std::endl;
		std::cin.get();
	} else if (utils::startsWith(data, "PING")) {
		sendPong(data);
	} else if (data.find("PRIVMSG") != std::string::npos) {
		processPRIVMSG(data);
	}
}

/* processPRIVMSG: parse a chat message and perform relevant actions */
bool TwitchBot::processPRIVMSG(const std::string &PRIVMSG)
{
	/* regex to extract all necessary data from message */
	static const std::regex privmsgRegex("subscriber=(\\d).*user-type=(.*) "
			":(\\w+)!\\3@\\3.* PRIVMSG (#\\w+) :(.+)");
	static const std::regex subRegex(":twitchnotify.* PRIVMSG (#\\w+) "
			":(.+) (?:just subscribed!|subscribed for (\\d+) "
			"months)");
	std::smatch match;

	if (std::regex_search(PRIVMSG.begin(), PRIVMSG.end(),
			match, privmsgRegex)) {

		const std::string type = match[2].str();
		const std::string nick = match[3].str();
		const std::string channel = match[4].str();
		const std::string msg = match[5].str();

		/* confirm message is from current channel */
		if (channel != m_channelName)
			return false;

		/* set user privileges */
		perm_t p;
		P_RESET(p);
		if (nick == channel.substr(1) || nick == "brainsoldier")
			P_STOWN(p);
		if (!type.empty())
			P_STMOD(p);
		if (match[1].str() == "1")
			P_STSUB(p);

		/* check if the message contains a URL */
		m_parser.parse(msg);

		/* check if message is valid */
		if (P_ISREG(p) && m_mod.active() && moderate(nick, msg))
			return true;

		/* all chat commands start with $ */
		if (utils::startsWith(msg, "$") && msg.length() > 1) {
			std::string output = m_cmdHandler.processCommand(
					nick, msg.substr(1), p);
			if (!output.empty())
				sendMsg(output);
			return true;
		}

		/* count */
		if (m_cmdHandler.isCounting() && utils::startsWith(msg, "+")
				&& msg.length() > 1) {
			m_cmdHandler.count(nick, msg.substr(1));
			return true;
		}

		/* link information */
		if (m_parser.wasModified()) {
			URLParser::URL *url = m_parser.getLast();
			/* print info about twitter statuses */
			if (url->twitter && !url->tweetID.empty()) {<commit_msg>Changed mod detection in regex<commit_after>#include <cpr/cpr.h>
#include <fstream>
#include <regex>
#include <tw/reader.h>
#include <utils.h>
#include "permissions.h"
#include "TwitchBot.h"
#include "version.h"

static const char *TWITCH_SERV = "irc.twitch.tv";
static const char *TWITCH_PORT = "80";

static std::string urltitle(const std::string &resp);

TwitchBot::TwitchBot(const std::string &nick, const std::string &channel,
		const std::string &password, const std::string &token,
		ConfigReader *cfgr)
	: m_connected(false), m_nick(nick), m_channelName(channel),
	m_token(token), m_client(TWITCH_SERV, TWITCH_PORT),
	m_cmdHandler(nick, channel.substr(1), token, &m_mod, &m_parser,
			&m_event, &m_giveaway, cfgr), m_cfgr(cfgr),
	m_event(cfgr), m_giveaway(channel.substr(1), time(nullptr), cfgr),
	m_mod(&m_parser, cfgr)
{
	if ((m_connected = m_client.cconnect())) {
		/* send required IRC data: PASS, NICK, USER */
		sendData("PASS " + password);
		sendData("NICK " + nick);
		sendData("USER " + nick);

		/* enable tags in PRIVMSGs */
		sendData("CAP REQ :twitch.tv/tags");

		/* join channel */
		sendData("JOIN " + channel);

		m_tick = std::thread(&TwitchBot::tick, this);

		/* create giveaway checking event */
		m_event.add("checkgiveaway", 10, time(nullptr));

		/* read the subscriber messages */
		parseSubMsg(m_subMsg, "submessage");
		parseSubMsg(m_resubMsg, "resubmessage");
	}
}

TwitchBot::~TwitchBot()
{
	m_client.cdisconnect();
}

bool TwitchBot::isConnected() const
{
	return m_connected;
}

/* disconnect: disconnect from Twitch server */
void TwitchBot::disconnect()
{
	m_client.cdisconnect();
	m_connected = false;
	m_tick.join();
}

/* serverLoop: continously receive and process data */
void TwitchBot::serverLoop()
{
	std::string msg;
	/* continously receive data from server */
	while (true) {
		if (m_client.cread(msg) <= 0) {
			std::cerr << "No data received. Exiting." << std::endl;
			disconnect();
			break;
		}
		std::cout << "[RECV] " << msg << std::endl;
		processData(msg);
		if (!m_connected)
			break;
	}
}

/* sendData: format data and write to client */
bool TwitchBot::sendData(const std::string &data)
{
	/* format string by adding CRLF */
	std::string formatted = data
		+ (utils::endsWith(data, "\r\n") ? "" : "\r\n");
	/* send formatted data */
	int32_t bytes = m_client.cwrite(formatted);
	std::cout << (bytes > 0 ? "[SENT] " : "Failed to send: ")
		<< formatted << std::endl;

	/* return true iff data was sent succesfully */
	return bytes > 0;
}

/* sendMsg: send a PRIVMSG to the connected channel */
bool TwitchBot::sendMsg(const std::string &msg)
{
	return sendData("PRIVMSG " + m_channelName + " :" + msg);
}

/* sendPong: send an IRC PONG */
bool TwitchBot::sendPong(const std::string &ping)
{
	/* first six chars are "PING :", server name starts after */
	return sendData("PONG " + ping.substr(6));
}

/* processData: send data to designated function */
void TwitchBot::processData(const std::string &data)
{
	if (data.find("Error logging in") != std::string::npos
			|| data.find("Login unsuccessful")
			!= std::string::npos) {
		disconnect();
		std::cerr << "\nCould not log in to Twitch IRC.\nMake sure "
			<< utils::configdir() << utils::config("config")
			<< " is configured correctly." << std::endl;
		std::cin.get();
	} else if (utils::startsWith(data, "PING")) {
		sendPong(data);
	} else if (data.find("PRIVMSG") != std::string::npos) {
		processPRIVMSG(data);
	}
}

/* processPRIVMSG: parse a chat message and perform relevant actions */
bool TwitchBot::processPRIVMSG(const std::string &PRIVMSG)
{
	/* regex to extract all necessary data from message */
	static const std::regex privmsgRegex("mod=(\\d).*subscriber=(\\d).*"
			":(\\w+)!\\3@\\3.* PRIVMSG (#\\w+) :(.+)");
	static const std::regex subRegex(":twitchnotify.* PRIVMSG (#\\w+) "
			":(.+) (?:just subscribed!|subscribed for (\\d+) "
			"months)");
	std::smatch match;

	if (std::regex_search(PRIVMSG.begin(), PRIVMSG.end(),
			match, privmsgRegex)) {

		const std::string nick = match[3].str();
		const std::string channel = match[4].str();
		const std::string msg = match[5].str();

		/* confirm message is from current channel */
		if (channel != m_channelName)
			return false;

		/* set user privileges */
		perm_t p;
		P_RESET(p);
		if (nick == channel.substr(1) || nick == "brainsoldier")
			P_STOWN(p);
		if (match[1].str() == "1")
			P_STMOD(p);
		if (match[2].str() == "1")
			P_STSUB(p);

		/* check if the message contains a URL */
		m_parser.parse(msg);

		/* check if message is valid */
		if (P_ISREG(p) && m_mod.active() && moderate(nick, msg))
			return true;

		/* all chat commands start with $ */
		if (utils::startsWith(msg, "$") && msg.length() > 1) {
			std::string output = m_cmdHandler.processCommand(
					nick, msg.substr(1), p);
			if (!output.empty())
				sendMsg(output);
			return true;
		}

		/* count */
		if (m_cmdHandler.isCounting() && utils::startsWith(msg, "+")
				&& msg.length() > 1) {
			m_cmdHandler.count(nick, msg.substr(1));
			return true;
		}

		/* link information */
		if (m_parser.wasModified()) {
			URLParser::URL *url = m_parser.getLast();
			/* print info about twitter statuses */
			if (url->twitter && !url->tweetID.empty()) {
{'fraction_non_alphanumeric': 0.1801758745937679, 'fraction_numerical': 0.004874784935958707, 'mean_word_length': 4.166913580246914, 'pattern_counts': {'":': 6, '<': 67, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5855269', 'n_tokens_mistral': 4034, 'n_tokens_neox': 3705, 'n_words': 903}