Prevent user from entering incorrect characters in Live Notifications channels list (#3715)
Co-authored-by: Sidd <iProdigy@users.noreply.github.com> Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
- Minor: Sorted usernames in /vips message to be case-insensitive. (#3696)
|
- Minor: Sorted usernames in /vips message to be case-insensitive. (#3696)
|
||||||
- Minor: Added option to open a user's chat in a new tab from the usercard profile picture context menu. (#3625)
|
- Minor: Added option to open a user's chat in a new tab from the usercard profile picture context menu. (#3625)
|
||||||
- Minor: Fixed tag parsing for consecutive escaped characters. (#3711)
|
- Minor: Fixed tag parsing for consecutive escaped characters. (#3711)
|
||||||
|
- Minor: Prevent user from entering incorrect characters in Live Notifications channels list. (#3715)
|
||||||
- Minor: Fixed automod caught message notice appearing twice for mods. (#3717)
|
- Minor: Fixed automod caught message notice appearing twice for mods. (#3717)
|
||||||
- Bugfix: Fixed live notifications for usernames containing uppercase characters. (#3646)
|
- Bugfix: Fixed live notifications for usernames containing uppercase characters. (#3646)
|
||||||
- Bugfix: Fixed live notifications not getting updated for closed streams going offline. (#3678)
|
- Bugfix: Fixed live notifications not getting updated for closed streams going offline. (#3678)
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ SOURCES += \
|
|||||||
src/widgets/helper/NotebookButton.cpp \
|
src/widgets/helper/NotebookButton.cpp \
|
||||||
src/widgets/helper/NotebookTab.cpp \
|
src/widgets/helper/NotebookTab.cpp \
|
||||||
src/widgets/helper/QColorPicker.cpp \
|
src/widgets/helper/QColorPicker.cpp \
|
||||||
|
src/widgets/helper/RegExpItemDelegate.cpp \
|
||||||
src/widgets/helper/ResizingTextEdit.cpp \
|
src/widgets/helper/ResizingTextEdit.cpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||||
src/widgets/helper/SearchPopup.cpp \
|
src/widgets/helper/SearchPopup.cpp \
|
||||||
@@ -588,6 +589,7 @@ HEADERS += \
|
|||||||
src/widgets/helper/NotebookButton.hpp \
|
src/widgets/helper/NotebookButton.hpp \
|
||||||
src/widgets/helper/NotebookTab.hpp \
|
src/widgets/helper/NotebookTab.hpp \
|
||||||
src/widgets/helper/QColorPicker.hpp \
|
src/widgets/helper/QColorPicker.hpp \
|
||||||
|
src/widgets/helper/RegExpItemDelegate.hpp \
|
||||||
src/widgets/helper/ResizingTextEdit.hpp \
|
src/widgets/helper/ResizingTextEdit.hpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.hpp \
|
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||||
src/widgets/helper/SearchPopup.hpp \
|
src/widgets/helper/SearchPopup.hpp \
|
||||||
|
|||||||
@@ -420,6 +420,8 @@ set(SOURCE_FILES
|
|||||||
widgets/helper/NotebookTab.hpp
|
widgets/helper/NotebookTab.hpp
|
||||||
widgets/helper/QColorPicker.cpp
|
widgets/helper/QColorPicker.cpp
|
||||||
widgets/helper/QColorPicker.hpp
|
widgets/helper/QColorPicker.hpp
|
||||||
|
widgets/helper/RegExpItemDelegate.cpp
|
||||||
|
widgets/helper/RegExpItemDelegate.hpp
|
||||||
widgets/helper/ResizingTextEdit.cpp
|
widgets/helper/ResizingTextEdit.cpp
|
||||||
widgets/helper/ResizingTextEdit.hpp
|
widgets/helper/ResizingTextEdit.hpp
|
||||||
widgets/helper/ScrollbarHighlight.cpp
|
widgets/helper/ScrollbarHighlight.cpp
|
||||||
|
|||||||
@@ -5,6 +5,12 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const auto TWITCH_USER_LOGIN_PATTERN = R"(^[a-z0-9]\w{0,24}$)";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void openTwitchUsercard(QString channel, QString username)
|
void openTwitchUsercard(QString channel, QString username)
|
||||||
{
|
{
|
||||||
QDesktopServices::openUrl("https://www.twitch.tv/popout/" + channel +
|
QDesktopServices::openUrl("https://www.twitch.tv/popout/" + channel +
|
||||||
@@ -35,4 +41,20 @@ void stripChannelName(QString &channelName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRegularExpression twitchUserNameRegexp()
|
||||||
|
{
|
||||||
|
static QRegularExpression re(
|
||||||
|
TWITCH_USER_LOGIN_PATTERN,
|
||||||
|
QRegularExpression::PatternOption::CaseInsensitiveOption);
|
||||||
|
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression twitchUserLoginRegexp()
|
||||||
|
{
|
||||||
|
static QRegularExpression re(TWITCH_USER_LOGIN_PATTERN);
|
||||||
|
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
@@ -12,4 +13,16 @@ void stripUserName(QString &userName);
|
|||||||
// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use
|
// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use
|
||||||
void stripChannelName(QString &channelName);
|
void stripChannelName(QString &channelName);
|
||||||
|
|
||||||
|
// Matches a strict Twitch user login.
|
||||||
|
// May contain lowercase a-z, 0-9, and underscores
|
||||||
|
// Must contain between 1 and 25 characters
|
||||||
|
// Must not start with an underscore
|
||||||
|
QRegularExpression twitchUserLoginRegexp();
|
||||||
|
|
||||||
|
// Matches a loose Twitch user login name.
|
||||||
|
// May contain lowercase and uppercase a-z, 0-9, and underscores
|
||||||
|
// Must contain between 1 and 25 characters
|
||||||
|
// Must not start with an underscore
|
||||||
|
QRegularExpression twitchUserNameRegexp();
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "EditableModelView.hpp"
|
#include "EditableModelView.hpp"
|
||||||
|
#include "widgets/helper/RegExpItemDelegate.hpp"
|
||||||
|
|
||||||
#include <QAbstractItemView>
|
#include <QAbstractItemView>
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
@@ -90,6 +91,10 @@ EditableModelView::EditableModelView(QAbstractTableModel *model, bool movable)
|
|||||||
// finish button layout
|
// finish button layout
|
||||||
buttons->addStretch(1);
|
buttons->addStretch(1);
|
||||||
}
|
}
|
||||||
|
void EditableModelView::setValidationRegexp(QRegularExpression regexp)
|
||||||
|
{
|
||||||
|
this->tableView_->setItemDelegate(new RegExpItemDelegate(this, regexp));
|
||||||
|
}
|
||||||
|
|
||||||
void EditableModelView::setTitles(std::initializer_list<QString> titles)
|
void EditableModelView::setTitles(std::initializer_list<QString> titles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public:
|
|||||||
EditableModelView(QAbstractTableModel *model, bool movable = true);
|
EditableModelView(QAbstractTableModel *model, bool movable = true);
|
||||||
|
|
||||||
void setTitles(std::initializer_list<QString> titles);
|
void setTitles(std::initializer_list<QString> titles);
|
||||||
|
void setValidationRegexp(QRegularExpression regexp);
|
||||||
|
|
||||||
QTableView *getTableView();
|
QTableView *getTableView();
|
||||||
QAbstractTableModel *getModel();
|
QAbstractTableModel *getModel();
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#include "widgets/helper/RegExpItemDelegate.hpp"
|
||||||
|
|
||||||
|
#include <QLineEdit>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
RegExpItemDelegate::RegExpItemDelegate(QObject *parent,
|
||||||
|
QRegularExpression regexp)
|
||||||
|
: QStyledItemDelegate(parent)
|
||||||
|
, regexp_(regexp)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *RegExpItemDelegate::createEditor(QWidget *parent,
|
||||||
|
const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
auto *editor = new QLineEdit(parent);
|
||||||
|
editor->setValidator(
|
||||||
|
new QRegularExpressionValidator(this->regexp_, editor));
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class RegExpItemDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
RegExpItemDelegate(QObject *parent, QRegularExpression regexp);
|
||||||
|
|
||||||
|
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QRegularExpression regexp_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
|
#include "util/Twitch.hpp"
|
||||||
#include "widgets/helper/EditableModelView.hpp"
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
@@ -96,6 +97,7 @@ NotificationPage::NotificationPage()
|
|||||||
nullptr, Platform::Twitch))
|
nullptr, Platform::Twitch))
|
||||||
.getElement();
|
.getElement();
|
||||||
view->setTitles({"Twitch channels"});
|
view->setTitles({"Twitch channels"});
|
||||||
|
view->setValidationRegexp(twitchUserNameRegexp());
|
||||||
|
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
|
|||||||
@@ -159,3 +159,108 @@ TEST(UtilTwitch, StripChannelName)
|
|||||||
<< qUtf8Printable(expectedChannelName);
|
<< qUtf8Printable(expectedChannelName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(UtilTwitch, UserLoginRegexp)
|
||||||
|
{
|
||||||
|
struct TestCase {
|
||||||
|
QString inputUserLogin;
|
||||||
|
bool matches;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TestCase> tests{
|
||||||
|
{
|
||||||
|
"pajlada",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Login names must not start with an underscore
|
||||||
|
"_pajlada",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@pajlada",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pajlada!",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pajlada,",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Login names must not contain capital letters
|
||||||
|
"Pajlada",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{"k", true},
|
||||||
|
{"testaccount_420", true},
|
||||||
|
{"3v", true},
|
||||||
|
{"ron", true},
|
||||||
|
{"bits", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto ®exp = twitchUserLoginRegexp();
|
||||||
|
|
||||||
|
for (const auto &[inputUserLogin, expectedMatch] : tests)
|
||||||
|
{
|
||||||
|
const auto &match = regexp.match(inputUserLogin);
|
||||||
|
auto actual = regexp.match(inputUserLogin);
|
||||||
|
|
||||||
|
EXPECT_EQ(match.hasMatch(), expectedMatch)
|
||||||
|
<< qUtf8Printable(inputUserLogin) << " did not match as expected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilTwitch, UserNameRegexp)
|
||||||
|
{
|
||||||
|
struct TestCase {
|
||||||
|
QString inputUserLogin;
|
||||||
|
bool matches;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TestCase> tests{
|
||||||
|
{"PAJLADA", true},
|
||||||
|
{
|
||||||
|
// User names must not start with an underscore
|
||||||
|
"_pajlada",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@pajlada",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pajlada!",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pajlada,",
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Pajlada",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{"k", true},
|
||||||
|
{"testaccount_420", true},
|
||||||
|
{"3v", true},
|
||||||
|
{"ron", true},
|
||||||
|
{"bits", true},
|
||||||
|
{"3V", true},
|
||||||
|
{"Ron", true},
|
||||||
|
{"Bits", true},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto ®exp = twitchUserNameRegexp();
|
||||||
|
|
||||||
|
for (const auto &[inputUserLogin, expectedMatch] : tests)
|
||||||
|
{
|
||||||
|
const auto &match = regexp.match(inputUserLogin);
|
||||||
|
auto actual = regexp.match(inputUserLogin);
|
||||||
|
|
||||||
|
EXPECT_EQ(match.hasMatch(), expectedMatch)
|
||||||
|
<< qUtf8Printable(inputUserLogin) << " did not match as expected";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user