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