Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: deduplicate IRC parsing #5678

Merged
merged 10 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
- Dev: Refactored static `MessageBuilder` helpers to standalone functions. (#5652)
- Dev: Decoupled reply parsing from `MessageBuilder`. (#5660, #5668)
- Dev: Refactored IRC message building. (#5663)
- Dev: Unified parsing of historic and live IRC messages. (#5678)

## 2.5.1

Expand Down
6 changes: 6 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(SOURCE_FILES
common/WindowDescriptors.cpp
common/WindowDescriptors.hpp

common/enums/MessageContext.hpp
common/enums/MessageOverflow.hpp

common/network/NetworkCommon.cpp
Expand Down Expand Up @@ -281,6 +282,9 @@ set(SOURCE_FILES
messages/MessageElement.cpp
messages/MessageElement.hpp
messages/MessageFlag.hpp
messages/MessageSimilarity.cpp
messages/MessageSimilarity.hpp
messages/MessageSink.hpp
messages/MessageThread.cpp
messages/MessageThread.hpp

Expand Down Expand Up @@ -526,6 +530,8 @@ set(SOURCE_FILES
util/Twitch.hpp
util/TypeName.hpp
util/Variant.hpp
util/VectorMessageSink.cpp
util/VectorMessageSink.hpp
util/WidgetHelpers.cpp
util/WidgetHelpers.hpp
util/WindowsHelper.cpp
Expand Down
26 changes: 23 additions & 3 deletions src/common/Channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include "Application.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageSimilarity.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Logging.hpp"
#include "singletons/Settings.hpp"
Expand Down Expand Up @@ -121,10 +123,10 @@ void Channel::addSystemMessage(const QString &contents)
this->addMessage(msg, MessageContext::Original);
}

void Channel::addOrReplaceTimeout(MessagePtr message)
void Channel::addOrReplaceTimeout(MessagePtr message, QTime now)
{
addOrReplaceChannelTimeout(
this->getMessageSnapshot(), std::move(message), QTime::currentTime(),
this->getMessageSnapshot(), std::move(message), now,
[this](auto /*idx*/, auto msg, auto replacement) {
this->replaceMessage(msg, replacement);
},
Expand Down Expand Up @@ -287,10 +289,15 @@ void Channel::clearMessages()
}

MessagePtr Channel::findMessage(QString messageID)
{
return this->findMessageByID(messageID);
}

MessagePtr Channel::findMessageByID(QStringView messageID)
{
MessagePtr res;

if (auto msg = this->messages_.rfind([&messageID](const MessagePtr &msg) {
if (auto msg = this->messages_.rfind([messageID](const MessagePtr &msg) {
return msg->id == messageID;
});
msg)
Expand All @@ -301,6 +308,19 @@ MessagePtr Channel::findMessage(QString messageID)
return res;
}

void Channel::applySimilarityFilters(const MessagePtr &message) const
{
setSimilarityFlags(message, this->messages_.getSnapshot());
}

MessageSinkTraits Channel::sinkTraits() const
{
return {
MessageSinkTrait::AddMentionsToGlobalChannel,
MessageSinkTrait::RequiresKnownChannelPointReward,
};
}

bool Channel::canSendMessage() const
{
return false;
Expand Down
28 changes: 14 additions & 14 deletions src/common/Channel.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#pragma once

#include "common/enums/MessageContext.hpp"
#include "controllers/completion/TabCompletionModel.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/MessageFlag.hpp"
#include "messages/MessageSink.hpp"

#include <magic_enum/magic_enum.hpp>
#include <pajlada/signals/signal.hpp>
Expand All @@ -26,15 +28,7 @@ enum class TimeoutStackStyle : int {
Default = DontStackBeyondUserMessage,
};

/// Context of the message being added to a channel
enum class MessageContext {
/// This message is the original
Original,
/// This message is a repost of a message that has already been added in a channel
Repost,
};

class Channel : public std::enable_shared_from_this<Channel>
class Channel : public std::enable_shared_from_this<Channel>, public MessageSink
{
public:
// This is for Lua. See scripts/make_luals_meta.py
Expand All @@ -55,7 +49,7 @@ class Channel : public std::enable_shared_from_this<Channel>
};

explicit Channel(const QString &name, Type type);
virtual ~Channel();
~Channel() override;

// SIGNALS
pajlada::Signals::Signal<const QString &, const QString &, bool &>
Expand Down Expand Up @@ -85,17 +79,18 @@ class Channel : public std::enable_shared_from_this<Channel>
// overridingFlags can be filled in with flags that should be used instead
// of the message's flags. This is useful in case a flag is specific to a
// type of split
void addMessage(MessagePtr message, MessageContext context,
std::optional<MessageFlags> overridingFlags = std::nullopt);
void addMessage(
MessagePtr message, MessageContext context,
std::optional<MessageFlags> overridingFlags = std::nullopt) final;
void addMessagesAtStart(const std::vector<MessagePtr> &messages_);

void addSystemMessage(const QString &contents);

/// Inserts the given messages in order by Message::serverReceivedTime.
void fillInMissingMessages(const std::vector<MessagePtr> &messages);

void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages();
void addOrReplaceTimeout(MessagePtr message, QTime now) final;
void disableAllMessages() final;
void replaceMessage(MessagePtr message, MessagePtr replacement);
void replaceMessage(size_t index, MessagePtr replacement);
void deleteMessage(QString messageID);
Expand All @@ -104,9 +99,14 @@ class Channel : public std::enable_shared_from_this<Channel>
void clearMessages();

MessagePtr findMessage(QString messageID);
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
MessagePtr findMessageByID(QStringView messageID) final;

bool hasMessages() const;

void applySimilarityFilters(const MessagePtr &message) const final;

MessageSinkTraits sinkTraits() const final;

// CHANNEL INFO
virtual bool canSendMessage() const;
virtual bool isWritable() const; // whether split input will be usable
Expand Down
13 changes: 13 additions & 0 deletions src/common/enums/MessageContext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

namespace chatterino {

/// Context of the message being added to a channel
enum class MessageContext {
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
pajlada marked this conversation as resolved.
Show resolved Hide resolved
/// This message is the original
Original,
/// This message is a repost of a message that has already been added in a channel
Repost,
};

} // namespace chatterino
51 changes: 51 additions & 0 deletions src/messages/MessageSimilarity.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "messages/MessageSimilarity.hpp"

#include <algorithm>
#include <vector>

namespace chatterino::similarity::detail {

float relativeSimilarity(QStringView str1, QStringView str2)
{
using SizeType = QStringView::size_type;

// Longest Common Substring Problem
std::vector<std::vector<int>> tree(str1.size(),
std::vector<int>(str2.size(), 0));
int z = 0;

for (SizeType i = 0; i < str1.size(); ++i)
{
for (SizeType j = 0; j < str2.size(); ++j)
{
if (str1[i] == str2[j])
{
if (i == 0 || j == 0)
{
tree[i][j] = 1;
}
else
{
tree[i][j] = tree[i - 1][j - 1] + 1;
}
z = std::max(tree[i][j], z);
}
else
{
tree[i][j] = 0;
}
}
}

// ensure that no div by 0
if (z == 0)
{
return 0.F;
}

auto div = std::max<>({static_cast<SizeType>(1), str1.size(), str2.size()});

return float(z) / float(div);
}

} // namespace chatterino::similarity::detail
73 changes: 73 additions & 0 deletions src/messages/MessageSimilarity.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#pragma once

#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "singletons/Settings.hpp"

#include <ranges>

namespace chatterino::similarity::detail {

float relativeSimilarity(QStringView str1, QStringView str2);

float inMessages(const MessagePtr &msg,
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
const std::ranges::bidirectional_range auto &messages)
{
float similarityPercent = 0.0F;

for (const auto &prevMsg :
messages | std::views::reverse |
std::views::take(getSettings()->hideSimilarMaxMessagesToCheck))
{
if (prevMsg->parseTime.secsTo(QTime::currentTime()) >=
getSettings()->hideSimilarMaxDelay)
{
break;
}
if (getSettings()->hideSimilarBySameUser &&
msg->loginName != prevMsg->loginName)
{
continue;
}
similarityPercent = std::max(
similarityPercent,
relativeSimilarity(msg->messageText, prevMsg->messageText));
}

return similarityPercent;
}

} // namespace chatterino::similarity::detail

namespace chatterino {

void setSimilarityFlags(const MessagePtr &message,
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
const std::ranges::bidirectional_range auto &messages)
{
if (getSettings()->similarityEnabled)
{
bool isMyself =
message->loginName ==
getApp()->getAccounts()->twitch.getCurrent()->getUserName();
bool hideMyself = getSettings()->hideSimilarMyself;

if (isMyself && !hideMyself)
{
return;
}

if (similarity::detail::inMessages(message, messages) >
getSettings()->similarityPercentage)
{
message->flags.set(MessageFlag::Similar);
if (getSettings()->colorSimilarDisabled)
{
message->flags.set(MessageFlag::Disabled);
}
}
}
}

} // namespace chatterino
58 changes: 58 additions & 0 deletions src/messages/MessageSink.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include "common/enums/MessageContext.hpp"
#include "messages/MessageFlag.hpp"

#include <memory>

class QStringView;
class QTime;

namespace chatterino {

struct Message;
using MessagePtr = std::shared_ptr<const Message>;

enum class MessageSinkTrait : uint8_t {
None = 0,
AddMentionsToGlobalChannel = 1 << 0,
RequiresKnownChannelPointReward = 1 << 1,
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
};
using MessageSinkTraits = FlagsEnum<MessageSinkTrait>;
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved

/// A generic interface for a managed buffer of `Message`s
class MessageSink
{
public:
virtual ~MessageSink() = default;

/// Add a message to this sink
///
/// @param message The message to add (non-null)
/// @param ctx The context in which this message is being added.
/// @param overridingFlags
virtual void addMessage(
MessagePtr message, MessageContext ctx,
std::optional<MessageFlags> overridingFlags = std::nullopt) = 0;
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved

/// Adds a timeout message or merges it into an existing one
virtual void addOrReplaceTimeout(MessagePtr clearchatMessage,
QTime now) = 0;

/// Flags all messages as `Disabled`
virtual void disableAllMessages() = 0;

/// Searches for similar messages and flags this message as similar
/// (based on the current settings).
virtual void applySimilarityFilters(const MessagePtr &message) const = 0;

/// @brief Searches for a message by an ID
///
/// If there is no message found, an empty shared-pointer is returned.
virtual MessagePtr findMessageByID(QStringView id) = 0;

///
Nerixyz marked this conversation as resolved.
Show resolved Hide resolved
virtual MessageSinkTraits sinkTraits() const = 0;
};

} // namespace chatterino
Loading
Loading