Add better support for IRC private messages (#4158)
Co-authored-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -84,6 +84,7 @@
|
|||||||
- Minor: Add setting to limit message input length. (#3418)
|
- Minor: Add setting to limit message input length. (#3418)
|
||||||
- Minor: Make built-in commands work in IRC channels. (#4160)
|
- Minor: Make built-in commands work in IRC channels. (#4160)
|
||||||
- Minor: Add support for `echo-message` capabilities for IRC. (#4157)
|
- Minor: Add support for `echo-message` capabilities for IRC. (#4157)
|
||||||
|
- Minor: Add proper support for IRC private messages. (#4158)
|
||||||
- Minor: Improved look of tabs when using a layout other than top. (#3925, #4152)
|
- Minor: Improved look of tabs when using a layout other than top. (#3925, #4152)
|
||||||
- Bugfix: Fixed channels with two leading `#`s not being usable on IRC (#4154)
|
- Bugfix: Fixed channels with two leading `#`s not being usable on IRC (#4154)
|
||||||
- Bugfix: Fixed `Add new account` dialog causing main chatterino window to be non movable. (#4121)
|
- Bugfix: Fixed `Add new account` dialog causing main chatterino window to be non movable. (#4121)
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
#include "providers/irc/IrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
@@ -234,102 +236,107 @@ QString runWhisperCommand(const QStringList &words, const ChannelPtr &channel)
|
|||||||
auto target = words.at(1);
|
auto target = words.at(1);
|
||||||
stripChannelName(target);
|
stripChannelName(target);
|
||||||
auto message = words.mid(2).join(' ');
|
auto message = words.mid(2).join(' ');
|
||||||
|
if (channel->isTwitchChannel())
|
||||||
if (useIrcForWhisperCommand())
|
|
||||||
{
|
{
|
||||||
if (channel->isTwitchChannel())
|
// this covers all twitch channels and twitch-like channels
|
||||||
|
if (useIrcForWhisperCommand())
|
||||||
{
|
{
|
||||||
appendWhisperMessageWordsLocally(words);
|
appendWhisperMessageWordsLocally(words);
|
||||||
sendWhisperMessage(words.join(' '));
|
sendWhisperMessage(words.join(' '));
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
else
|
getHelix()->getUserByName(
|
||||||
{
|
target,
|
||||||
channel->addMessage(makeSystemMessage(
|
[channel, currentUser, target, message,
|
||||||
"You can only send whispers from Twitch channels."));
|
words](const auto &targetUser) {
|
||||||
}
|
getHelix()->sendWhisper(
|
||||||
|
currentUser->getUserId(), targetUser.id, message,
|
||||||
|
[words] {
|
||||||
|
appendWhisperMessageWordsLocally(words);
|
||||||
|
},
|
||||||
|
[channel, target, targetUser](auto error, auto message) {
|
||||||
|
using Error = HelixWhisperError;
|
||||||
|
|
||||||
|
QString errorMessage = "Failed to send whisper - ";
|
||||||
|
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case Error::NoVerifiedPhone: {
|
||||||
|
errorMessage +=
|
||||||
|
"Due to Twitch restrictions, you are now "
|
||||||
|
"required to have a verified phone number "
|
||||||
|
"to send whispers. You can add a phone "
|
||||||
|
"number in Twitch settings. "
|
||||||
|
"https://www.twitch.tv/settings/security";
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::RecipientBlockedUser: {
|
||||||
|
errorMessage +=
|
||||||
|
"The recipient doesn't allow whispers "
|
||||||
|
"from strangers or you directly.";
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::WhisperSelf: {
|
||||||
|
errorMessage += "You cannot whisper yourself.";
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::Forwarded: {
|
||||||
|
errorMessage += message;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::Ratelimited: {
|
||||||
|
errorMessage +=
|
||||||
|
"You may only whisper a maximum of 40 "
|
||||||
|
"unique recipients per day. Within the "
|
||||||
|
"per day limit, you may whisper a "
|
||||||
|
"maximum of 3 whispers per second and "
|
||||||
|
"a maximum of 100 whispers per minute.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::UserMissingScope: {
|
||||||
|
// TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE
|
||||||
|
errorMessage += "Missing required scope. "
|
||||||
|
"Re-login with your "
|
||||||
|
"account and try again.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::UserNotAuthorized: {
|
||||||
|
// TODO(pajlada): Phrase MISSING_PERMISSION
|
||||||
|
errorMessage += "You don't have permission to "
|
||||||
|
"perform that action.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::Unknown: {
|
||||||
|
errorMessage +=
|
||||||
|
"An unknown error has occurred.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel->addMessage(makeSystemMessage(errorMessage));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[channel] {
|
||||||
|
channel->addMessage(
|
||||||
|
makeSystemMessage("No user matching that username."));
|
||||||
|
});
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
// we must be on IRC
|
||||||
getHelix()->getUserByName(
|
auto *ircChannel = dynamic_cast<IrcChannel *>(channel.get());
|
||||||
target,
|
if (ircChannel == nullptr)
|
||||||
[channel, currentUser, target, message, words](const auto &targetUser) {
|
{
|
||||||
getHelix()->sendWhisper(
|
// give up
|
||||||
currentUser->getUserId(), targetUser.id, message,
|
return "";
|
||||||
[words] {
|
}
|
||||||
appendWhisperMessageWordsLocally(words);
|
auto *server = ircChannel->server();
|
||||||
},
|
server->sendWhisper(target, message);
|
||||||
[channel, target, targetUser](auto error, auto message) {
|
|
||||||
using Error = HelixWhisperError;
|
|
||||||
|
|
||||||
QString errorMessage = "Failed to send whisper - ";
|
|
||||||
|
|
||||||
switch (error)
|
|
||||||
{
|
|
||||||
case Error::NoVerifiedPhone: {
|
|
||||||
errorMessage +=
|
|
||||||
"Due to Twitch restrictions, you are now "
|
|
||||||
"required to have a verified phone number "
|
|
||||||
"to send whispers. You can add a phone "
|
|
||||||
"number in Twitch settings. "
|
|
||||||
"https://www.twitch.tv/settings/security";
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::RecipientBlockedUser: {
|
|
||||||
errorMessage +=
|
|
||||||
"The recipient doesn't allow whispers "
|
|
||||||
"from strangers or you directly.";
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::WhisperSelf: {
|
|
||||||
errorMessage += "You cannot whisper yourself.";
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::Forwarded: {
|
|
||||||
errorMessage += message;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::Ratelimited: {
|
|
||||||
errorMessage +=
|
|
||||||
"You may only whisper a maximum of 40 "
|
|
||||||
"unique recipients per day. Within the "
|
|
||||||
"per day limit, you may whisper a "
|
|
||||||
"maximum of 3 whispers per second and "
|
|
||||||
"a maximum of 100 whispers per minute.";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::UserMissingScope: {
|
|
||||||
// TODO(pajlada): Phrase MISSING_REQUIRED_SCOPE
|
|
||||||
errorMessage += "Missing required scope. "
|
|
||||||
"Re-login with your "
|
|
||||||
"account and try again.";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::UserNotAuthorized: {
|
|
||||||
// TODO(pajlada): Phrase MISSING_PERMISSION
|
|
||||||
errorMessage += "You don't have permission to "
|
|
||||||
"perform that action.";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Error::Unknown: {
|
|
||||||
errorMessage += "An unknown error has occurred.";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
channel->addMessage(makeSystemMessage(errorMessage));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[channel] {
|
|
||||||
channel->addMessage(
|
|
||||||
makeSystemMessage("No user matching that username."));
|
|
||||||
});
|
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ Outcome invokeIrcCommand(const QString &commandName, const QString &allParams,
|
|||||||
|
|
||||||
if (cmd == "msg")
|
if (cmd == "msg")
|
||||||
{
|
{
|
||||||
sendRaw("PRIVMSG " + params[0] + " :" + paramsAfter(0));
|
channel.server()->sendWhisper(params[0], paramsAfter(0));
|
||||||
}
|
}
|
||||||
else if (cmd == "away")
|
else if (cmd == "away")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/MessageColor.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
@@ -41,6 +42,15 @@ IrcMessageBuilder::IrcMessageBuilder(
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IrcMessageBuilder::IrcMessageBuilder(
|
||||||
|
const Communi::IrcPrivateMessage *_ircMessage,
|
||||||
|
const MessageParseArgs &_args)
|
||||||
|
: SharedMessageBuilder(Channel::getEmpty().get(), _ircMessage, _args,
|
||||||
|
_ircMessage->content(), false)
|
||||||
|
, whisperTarget_(_ircMessage->target())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
MessagePtr IrcMessageBuilder::build()
|
MessagePtr IrcMessageBuilder::build()
|
||||||
{
|
{
|
||||||
// PARSE
|
// PARSE
|
||||||
@@ -93,7 +103,32 @@ void IrcMessageBuilder::appendUsername()
|
|||||||
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
||||||
MessageColor::System, FontStyle::ChatMedium);
|
MessageColor::System, FontStyle::ChatMedium);
|
||||||
|
|
||||||
this->emplace<TextElement>("you:", MessageElementFlag::Username);
|
if (this->whisperTarget_.isEmpty())
|
||||||
|
{
|
||||||
|
this->emplace<TextElement>("you:", MessageElementFlag::Username);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->emplace<TextElement>(this->whisperTarget_ + ":",
|
||||||
|
MessageElementFlag::Username,
|
||||||
|
getRandomColor(this->whisperTarget_),
|
||||||
|
FontStyle::ChatMediumBold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this->args.isSentWhisper)
|
||||||
|
{
|
||||||
|
this->emplace<TextElement>(usernameText, MessageElementFlag::Username,
|
||||||
|
this->usernameColor_,
|
||||||
|
FontStyle::ChatMediumBold);
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
this->emplace<TextElement>("->", MessageElementFlag::Username,
|
||||||
|
MessageColor::System, FontStyle::ChatMedium);
|
||||||
|
|
||||||
|
this->emplace<TextElement>(
|
||||||
|
this->whisperTarget_ + ":", MessageElementFlag::Username,
|
||||||
|
getRandomColor(this->whisperTarget_), FontStyle::ChatMediumBold)
|
||||||
|
->setLink({Link::UserWhisper, this->whisperTarget_});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,10 +36,23 @@ public:
|
|||||||
explicit IrcMessageBuilder(const Communi::IrcNoticeMessage *_ircMessage,
|
explicit IrcMessageBuilder(const Communi::IrcNoticeMessage *_ircMessage,
|
||||||
const MessageParseArgs &_args);
|
const MessageParseArgs &_args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief used for whisper messages (i.e. PRIVMSG messages with our nick as the target)
|
||||||
|
**/
|
||||||
|
explicit IrcMessageBuilder(const Communi::IrcPrivateMessage *_ircMessage,
|
||||||
|
const MessageParseArgs &_args);
|
||||||
|
|
||||||
MessagePtr build() override;
|
MessagePtr build() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void appendUsername();
|
void appendUsername();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief holds the name of the target for the private/direct IRC message
|
||||||
|
*
|
||||||
|
* This might not be our nick
|
||||||
|
*/
|
||||||
|
QString whisperTarget_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
#include "messages/MessageColor.hpp"
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/irc/Irc2.hpp"
|
#include "providers/irc/Irc2.hpp"
|
||||||
#include "providers/irc/IrcChannel2.hpp"
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
#include "providers/irc/IrcMessageBuilder.hpp"
|
||||||
@@ -190,6 +192,36 @@ void IrcServer::onReadConnected(IrcConnection *connection)
|
|||||||
|
|
||||||
void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||||
{
|
{
|
||||||
|
// Note: This doesn't use isPrivate() because it only applies to messages targeting our user,
|
||||||
|
// Servers or bouncers may send messages which have our user as the source
|
||||||
|
// (like with echo-message CAP), we need to take care of this.
|
||||||
|
if (!message->target().startsWith("#"))
|
||||||
|
{
|
||||||
|
MessageParseArgs args;
|
||||||
|
if (message->isOwn())
|
||||||
|
{
|
||||||
|
// The server sent us a whisper which has our user as the source
|
||||||
|
args.isSentWhisper = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.isReceivedWhisper = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IrcMessageBuilder builder(message, args);
|
||||||
|
|
||||||
|
auto msg = builder.build();
|
||||||
|
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
{
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
{
|
||||||
|
shared->addMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto target = message->target();
|
auto target = message->target();
|
||||||
target = target.startsWith('#') ? target.mid(1) : target;
|
target = target.startsWith('#') ? target.mid(1) : target;
|
||||||
|
|
||||||
@@ -299,6 +331,38 @@ void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IrcServer::sendWhisper(const QString &target, const QString &message)
|
||||||
|
{
|
||||||
|
this->sendRawMessage(QString("PRIVMSG %1 :%2").arg(target, message));
|
||||||
|
if (this->hasEcho())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageParseArgs args;
|
||||||
|
args.isSentWhisper = true;
|
||||||
|
|
||||||
|
MessageBuilder b;
|
||||||
|
|
||||||
|
b.emplace<TimestampElement>();
|
||||||
|
b.emplace<TextElement>(this->nick(), MessageElementFlag::Text,
|
||||||
|
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||||
|
b.emplace<TextElement>("->", MessageElementFlag::Text,
|
||||||
|
MessageColor::System);
|
||||||
|
b.emplace<TextElement>(target + ":", MessageElementFlag::Text,
|
||||||
|
MessageColor::Text, FontStyle::ChatMediumBold);
|
||||||
|
b.emplace<TextElement>(message, MessageElementFlag::Text);
|
||||||
|
|
||||||
|
auto msg = b.release();
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
{
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
{
|
||||||
|
shared->addMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool IrcServer::hasEcho() const
|
bool IrcServer::hasEcho() const
|
||||||
{
|
{
|
||||||
return this->hasEcho_;
|
return this->hasEcho_;
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public:
|
|||||||
const QString &userFriendlyIdentifier();
|
const QString &userFriendlyIdentifier();
|
||||||
|
|
||||||
bool hasEcho() const;
|
bool hasEcho() const;
|
||||||
|
/**
|
||||||
|
* @brief sends a whisper to the target user (PRIVMSG where a user is the target)
|
||||||
|
*/
|
||||||
|
void sendWhisper(const QString &target, const QString &message);
|
||||||
|
|
||||||
// AbstractIrcServer interface
|
// AbstractIrcServer interface
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
Reference in New Issue
Block a user