1 #include "TwitchIrcServer.hpp"
2
3 #include <IrcCommand>
4 #include <cassert>
5
6 #include "Application.hpp"
7 #include "common/Common.hpp"
8 #include "common/Env.hpp"
9 #include "common/QLogging.hpp"
10 #include "controllers/accounts/AccountController.hpp"
11 #include "messages/Message.hpp"
12 #include "messages/MessageBuilder.hpp"
13 #include "providers/twitch/IrcMessageHandler.hpp"
14 #include "providers/twitch/PubsubClient.hpp"
15 #include "providers/twitch/TwitchAccount.hpp"
16 #include "providers/twitch/TwitchChannel.hpp"
17 #include "providers/twitch/TwitchHelpers.hpp"
18 #include "util/PostToThread.hpp"
19
20 #include <QMetaEnum>
21
22 // using namespace Communi;
23 using namespace std::chrono_literals;
24
25 namespace chatterino {
26
TwitchIrcServer()27 TwitchIrcServer::TwitchIrcServer()
28 : whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
29 , mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
30 , watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
31 , liveChannel(new Channel("/live", Channel::Type::TwitchLive))
32 {
33 this->initializeIrc();
34
35 this->pubsub = new PubSub;
36
37 // getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
38 // this->connect(); },
39 // this->signalHolder_,
40 // false);
41 }
42
initialize(Settings & settings,Paths & paths)43 void TwitchIrcServer::initialize(Settings &settings, Paths &paths)
44 {
45 getApp()->accounts->twitch.currentUserChanged.connect([this]() {
46 postToThread([this] {
47 this->connect();
48 });
49 });
50
51 this->bttv.loadEmotes();
52 this->ffz.loadEmotes();
53 }
54
initializeConnection(IrcConnection * connection,ConnectionType type)55 void TwitchIrcServer::initializeConnection(IrcConnection *connection,
56 ConnectionType type)
57 {
58 std::shared_ptr<TwitchAccount> account =
59 getApp()->accounts->twitch.getCurrent();
60
61 qCDebug(chatterinoTwitch) << "logging in as" << account->getUserName();
62
63 // twitch.tv/tags enables IRCv3 tags on messages. See https://dev.twitch.tv/docs/irc/tags
64 // twitch.tv/commands enables a bunch of miscellaneous command capabilities. See https://dev.twitch.tv/docs/irc/commands
65 // twitch.tv/membership enables the JOIN/PART/NAMES commands. See https://dev.twitch.tv/docs/irc/membership
66 // This is enabled so we receive USERSTATE messages when joining channels / typing messages, along with the other command capabilities
67 QStringList caps{"twitch.tv/tags", "twitch.tv/commands"};
68 if (type != ConnectionType::Write)
69 {
70 caps.push_back("twitch.tv/membership");
71 }
72
73 connection->network()->setSkipCapabilityValidation(true);
74 connection->network()->setRequestedCapabilities(caps);
75
76 QString username = account->getUserName();
77 QString oauthToken = account->getOAuthToken();
78
79 if (!oauthToken.startsWith("oauth:"))
80 {
81 oauthToken.prepend("oauth:");
82 }
83
84 connection->setUserName(username);
85 connection->setNickName(username);
86 connection->setRealName(username);
87
88 if (!account->isAnon())
89 {
90 connection->setPassword(oauthToken);
91 }
92
93 // https://dev.twitch.tv/docs/irc/guide/#connecting-to-twitch-irc
94 // SSL disabled: irc://irc.chat.twitch.tv:6667 (or port 80)
95 // SSL enabled: irc://irc.chat.twitch.tv:6697 (or port 443)
96 connection->setHost(Env::get().twitchServerHost);
97 connection->setPort(Env::get().twitchServerPort);
98 connection->setSecure(Env::get().twitchServerSecure);
99
100 this->open(type);
101 }
102
createChannel(const QString & channelName)103 std::shared_ptr<Channel> TwitchIrcServer::createChannel(
104 const QString &channelName)
105 {
106 auto channel =
107 std::shared_ptr<TwitchChannel>(new TwitchChannel(channelName));
108 channel->initialize();
109
110 channel->sendMessageSignal.connect(
111 [this, channel = channel.get()](auto &chan, auto &msg, bool &sent) {
112 this->onMessageSendRequested(channel, msg, sent);
113 });
114
115 return std::shared_ptr<Channel>(channel);
116 }
117
privateMessageReceived(Communi::IrcPrivateMessage * message)118 void TwitchIrcServer::privateMessageReceived(
119 Communi::IrcPrivateMessage *message)
120 {
121 IrcMessageHandler::instance().handlePrivMessage(message, *this);
122 }
123
readConnectionMessageReceived(Communi::IrcMessage * message)124 void TwitchIrcServer::readConnectionMessageReceived(
125 Communi::IrcMessage *message)
126 {
127 AbstractIrcServer::readConnectionMessageReceived(message);
128
129 if (message->type() == Communi::IrcMessage::Type::Private)
130 {
131 // We already have a handler for private messages
132 return;
133 }
134
135 const QString &command = message->command();
136
137 auto &handler = IrcMessageHandler::instance();
138
139 // Below commands enabled through the twitch.tv/membership CAP REQ
140 if (command == "JOIN")
141 {
142 handler.handleJoinMessage(message);
143 }
144 else if (command == "PART")
145 {
146 handler.handlePartMessage(message);
147 }
148 else if (command == "USERSTATE")
149 {
150 // Received USERSTATE upon JOINing a channel
151 handler.handleUserStateMessage(message);
152 }
153 else if (command == "ROOMSTATE")
154 {
155 // Received ROOMSTATE upon JOINing a channel
156 handler.handleRoomStateMessage(message);
157 }
158 else if (command == "CLEARCHAT")
159 {
160 handler.handleClearChatMessage(message);
161 }
162 else if (command == "CLEARMSG")
163 {
164 handler.handleClearMessageMessage(message);
165 }
166 else if (command == "USERNOTICE")
167 {
168 handler.handleUserNoticeMessage(message, *this);
169 }
170 else if (command == "NOTICE")
171 {
172 handler.handleNoticeMessage(
173 static_cast<Communi::IrcNoticeMessage *>(message));
174 }
175 else if (command == "WHISPER")
176 {
177 handler.handleWhisperMessage(message);
178 }
179 else if (command == "RECONNECT")
180 {
181 this->addGlobalSystemMessage(
182 "Twitch Servers requested us to reconnect, reconnecting");
183 this->connect();
184 }
185 else if (command == "GLOBALUSERSTATE")
186 {
187 handler.handleGlobalUserStateMessage(message);
188 }
189 }
190
writeConnectionMessageReceived(Communi::IrcMessage * message)191 void TwitchIrcServer::writeConnectionMessageReceived(
192 Communi::IrcMessage *message)
193 {
194 const QString &command = message->command();
195
196 auto &handler = IrcMessageHandler::instance();
197 // Below commands enabled through the twitch.tv/commands CAP REQ
198 if (command == "USERSTATE")
199 {
200 // Received USERSTATE upon sending PRIVMSG messages
201 handler.handleUserStateMessage(message);
202 }
203 else if (command == "NOTICE")
204 {
205 // List of expected NOTICE messages on write connection
206 // https://git.kotmisia.pl/Mm2PL/docs/src/branch/master/irc_msg_ids.md#command-results
207 handler.handleNoticeMessage(
208 static_cast<Communi::IrcNoticeMessage *>(message));
209 }
210 else if (command == "RECONNECT")
211 {
212 this->addGlobalSystemMessage(
213 "Twitch Servers requested us to reconnect, reconnecting");
214 this->connect();
215 }
216 }
217
getCustomChannel(const QString & channelName)218 std::shared_ptr<Channel> TwitchIrcServer::getCustomChannel(
219 const QString &channelName)
220 {
221 if (channelName == "/whispers")
222 {
223 return this->whispersChannel;
224 }
225
226 if (channelName == "/mentions")
227 {
228 return this->mentionsChannel;
229 }
230
231 if (channelName == "/live")
232 {
233 return this->liveChannel;
234 }
235
236 if (channelName == "$$$")
237 {
238 static auto channel =
239 std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
240 static auto getTimer = [&] {
241 for (auto i = 0; i < 1000; i++)
242 {
243 channel->addMessage(makeSystemMessage(QString::number(i + 1)));
244 }
245
246 auto timer = new QTimer;
247 QObject::connect(timer, &QTimer::timeout, [] {
248 channel->addMessage(
249 makeSystemMessage(QTime::currentTime().toString()));
250 });
251 timer->start(500);
252 return timer;
253 }();
254
255 return channel;
256 }
257
258 return nullptr;
259 }
260
forEachChannelAndSpecialChannels(std::function<void (ChannelPtr)> func)261 void TwitchIrcServer::forEachChannelAndSpecialChannels(
262 std::function<void(ChannelPtr)> func)
263 {
264 this->forEachChannel(func);
265
266 func(this->whispersChannel);
267 func(this->mentionsChannel);
268 func(this->liveChannel);
269 }
270
getChannelOrEmptyByID(const QString & channelId)271 std::shared_ptr<Channel> TwitchIrcServer::getChannelOrEmptyByID(
272 const QString &channelId)
273 {
274 std::lock_guard<std::mutex> lock(this->channelMutex);
275
276 for (const auto &weakChannel : this->channels)
277 {
278 auto channel = weakChannel.lock();
279 if (!channel)
280 continue;
281
282 auto twitchChannel = std::dynamic_pointer_cast<TwitchChannel>(channel);
283 if (!twitchChannel)
284 continue;
285
286 if (twitchChannel->roomId() == channelId &&
287 twitchChannel->getName().splitRef(":").size() < 3)
288 {
289 return twitchChannel;
290 }
291 }
292
293 return Channel::getEmpty();
294 }
295
cleanChannelName(const QString & dirtyChannelName)296 QString TwitchIrcServer::cleanChannelName(const QString &dirtyChannelName)
297 {
298 if (dirtyChannelName.startsWith('#'))
299 return dirtyChannelName.mid(1).toLower();
300 else
301 return dirtyChannelName.toLower();
302 }
303
hasSeparateWriteConnection() const304 bool TwitchIrcServer::hasSeparateWriteConnection() const
305 {
306 return true;
307 // return getSettings()->twitchSeperateWriteConnection;
308 }
309
onMessageSendRequested(TwitchChannel * channel,const QString & message,bool & sent)310 void TwitchIrcServer::onMessageSendRequested(TwitchChannel *channel,
311 const QString &message, bool &sent)
312 {
313 sent = false;
314
315 {
316 std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
317
318 // std::queue<std::chrono::steady_clock::time_point>
319 auto &lastMessage = channel->hasHighRateLimit()
320 ? this->lastMessageMod_
321 : this->lastMessagePleb_;
322 size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
323 auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
324
325 auto now = std::chrono::steady_clock::now();
326
327 // check if you are sending messages too fast
328 if (!lastMessage.empty() && lastMessage.back() + minMessageOffset > now)
329 {
330 if (this->lastErrorTimeSpeed_ + 30s < now)
331 {
332 auto errorMessage =
333 makeSystemMessage("You are sending messages too quickly.");
334
335 channel->addMessage(errorMessage);
336
337 this->lastErrorTimeSpeed_ = now;
338 }
339 return;
340 }
341
342 // remove messages older than 30 seconds
343 while (!lastMessage.empty() && lastMessage.front() + 32s < now)
344 {
345 lastMessage.pop();
346 }
347
348 // check if you are sending too many messages
349 if (lastMessage.size() >= maxMessageCount)
350 {
351 if (this->lastErrorTimeAmount_ + 30s < now)
352 {
353 auto errorMessage =
354 makeSystemMessage("You are sending too many messages.");
355
356 channel->addMessage(errorMessage);
357
358 this->lastErrorTimeAmount_ = now;
359 }
360 return;
361 }
362
363 lastMessage.push(now);
364 }
365
366 this->sendMessage(channel->getName(), message);
367 sent = true;
368 }
369
getBttvEmotes() const370 const BttvEmotes &TwitchIrcServer::getBttvEmotes() const
371 {
372 return this->bttv;
373 }
getFfzEmotes() const374 const FfzEmotes &TwitchIrcServer::getFfzEmotes() const
375 {
376 return this->ffz;
377 }
378
379 } // namespace chatterino
380