diff --git a/.CI/CreateDMG.sh b/.CI/CreateDMG.sh
index 60a8f534..8ad191e2 100755
--- a/.CI/CreateDMG.sh
+++ b/.CI/CreateDMG.sh
@@ -1,13 +1,13 @@
#!/bin/sh
echo "Running MACDEPLOYQT"
-/usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
-echo "Creating APP folder"
-mkdir app
-echo "Running hdiutil attach on the built DMG"
-hdiutil attach chatterino.dmg
-echo "Copying chatterino.app into the app folder"
-cp -r /Volumes/chatterino/chatterino.app app/
-echo "Creating DMG with create-dmg"
-create-dmg --volname Chatterino2 --volicon ../resources/chatterino.icns --icon-size 50 --app-drop-link 0 0 --format UDBZ chatterino-osx.dmg app/
-echo "DONE"
+/usr/local/opt/qt/bin/macdeployqt chatterino.app
+echo "Creating python3 virtual environment"
+python3 -m venv venv
+echo "Entering python3 virtual environment"
+. venv/bin/activate
+echo "Installing dmgbuild"
+python3 -m pip install dmgbuild
+echo "Running dmgbuild.."
+dmgbuild --settings ./../.CI/dmg-settings.py -D app=./chatterino.app Chatterino2 chatterino-osx.dmg
+echo "Done!"
diff --git a/.CI/dmg-settings.py b/.CI/dmg-settings.py
new file mode 100644
index 00000000..6b068fa1
--- /dev/null
+++ b/.CI/dmg-settings.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import biplist
+import os.path
+
+#
+# Example settings file for dmgbuild
+#
+
+# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg
+
+# You can actually use this file for your own application (not just TextEdit)
+# by doing e.g.
+#
+# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg
+
+# .. Useful stuff ..............................................................
+
+application = defines.get('app', '/Applications/TextEdit.app')
+appname = os.path.basename(application)
+
+def icon_from_app(app_path):
+ plist_path = os.path.join(app_path, 'Contents', 'Info.plist')
+ plist = biplist.readPlist(plist_path)
+ icon_name = plist['CFBundleIconFile']
+ icon_root,icon_ext = os.path.splitext(icon_name)
+ if not icon_ext:
+ icon_ext = '.icns'
+ icon_name = icon_root + icon_ext
+ return os.path.join(app_path, 'Contents', 'Resources', icon_name)
+
+# .. Basics ....................................................................
+
+# Uncomment to override the output filename
+# filename = 'test.dmg'
+
+# Uncomment to override the output volume name
+# volume_name = 'Test'
+
+# Volume format (see hdiutil create -help)
+format = defines.get('format', 'UDBZ')
+
+# Volume size
+size = defines.get('size', None)
+
+# Files to include
+files = [ application ]
+
+# Symlinks to create
+symlinks = { 'Applications': '/Applications' }
+
+# Volume icon
+#
+# You can either define icon, in which case that icon file will be copied to the
+# image, *or* you can define badge_icon, in which case the icon file you specify
+# will be used to badge the system's Removable Disk icon
+#
+#icon = '/path/to/icon.icns'
+badge_icon = icon_from_app(application)
+
+# Where to put the icons
+icon_locations = {
+ appname: (140, 120),
+ 'Applications': (500, 120)
+ }
+
+# .. Window configuration ......................................................
+
+# Background
+#
+# This is a STRING containing any of the following:
+#
+# #3344ff - web-style RGB color
+# #34f - web-style RGB color, short form (#34f == #3344ff)
+# rgb(1,0,0) - RGB color, each value is between 0 and 1
+# hsl(120,1,.5) - HSL (hue saturation lightness) color
+# hwb(300,0,0) - HWB (hue whiteness blackness) color
+# cmyk(0,1,0,0) - CMYK color
+# goldenrod - X11/SVG named color
+# builtin-arrow - A simple built-in background with a blue arrow
+# /foo/bar/baz.png - The path to an image file
+#
+# The hue component in hsl() and hwb() may include a unit; it defaults to
+# degrees ('deg'), but also supports radians ('rad') and gradians ('grad'
+# or 'gon').
+#
+# Other color components may be expressed either in the range 0 to 1, or
+# as percentages (e.g. 60% is equivalent to 0.6).
+background = 'builtin-arrow'
+
+show_status_bar = False
+show_tab_view = False
+show_toolbar = False
+show_pathbar = False
+show_sidebar = False
+sidebar_width = 180
+
+# Window position in ((x, y), (w, h)) format
+window_rect = ((100, 100), (640, 280))
+
+# Select the default view; must be one of
+#
+# 'icon-view'
+# 'list-view'
+# 'column-view'
+# 'coverflow'
+#
+default_view = 'icon-view'
+
+# General view configuration
+show_icon_preview = False
+
+# Set these to True to force inclusion of icon/list view settings (otherwise
+# we only include settings for the default view)
+include_icon_view_settings = 'auto'
+include_list_view_settings = 'auto'
+
+# .. Icon view configuration ...................................................
+
+arrange_by = None
+grid_offset = (0, 0)
+grid_spacing = 100
+scroll_position = (0, 0)
+label_pos = 'bottom' # or 'right'
+text_size = 16
+icon_size = 128
+
+# .. List view configuration ...................................................
+
+# Column names are as follows:
+#
+# name
+# date-modified
+# date-created
+# date-added
+# date-last-opened
+# size
+# kind
+# label
+# version
+# comments
+#
+list_icon_size = 16
+list_text_size = 12
+list_scroll_position = (0, 0)
+list_sort_by = 'name'
+list_use_relative_dates = True
+list_calculate_all_sizes = False,
+list_columns = ('name', 'date-modified', 'size', 'kind', 'date-added')
+list_column_widths = {
+ 'name': 300,
+ 'date-modified': 181,
+ 'date-created': 181,
+ 'date-added': 181,
+ 'date-last-opened': 181,
+ 'size': 97,
+ 'kind': 115,
+ 'label': 100,
+ 'version': 75,
+ 'comments': 300,
+ }
+list_column_sort_directions = {
+ 'name': 'ascending',
+ 'date-modified': 'descending',
+ 'date-created': 'descending',
+ 'date-added': 'descending',
+ 'date-last-opened': 'descending',
+ 'size': 'descending',
+ 'kind': 'ascending',
+ 'label': 'ascending',
+ 'version': 'ascending',
+ 'comments': 'ascending',
+ }
+
+# .. License configuration .....................................................
+
+# Text in the license configuration is stored in the resources, which means
+# it gets stored in a legacy Mac encoding according to the language. dmgbuild
+# will *try* to convert Unicode strings to the appropriate encoding, *but*
+# you should be aware that Python doesn't support all of the necessary encodings;
+# in many cases you will need to encode the text yourself and use byte strings
+# instead here.
+
+# Recognized language names are:
+#
+# af_ZA, ar, be_BY, bg_BG, bn, bo, br, ca_ES, cs_CZ, cy, da_DK, de_AT, de_CH,
+# de_DE, dz_BT, el_CY, el_GR, en_AU, en_CA, en_GB, en_IE, en_SG, en_US, eo,
+# es_419, es_ES, et_EE, fa_IR, fi_FI, fo_FO, fr_001, fr_BE, fr_CA, fr_CH,
+# fr_FR, ga-Latg_IE, ga_IE, gd, grc, gu_IN, gv, he_IL, hi_IN, hr_HR, hu_HU,
+# hy_AM, is_IS, it_CH, it_IT, iu_CA, ja_JP, ka_GE, kl, ko_KR, lt_LT, lv_LV,
+# mk_MK, mr_IN, mt_MT, nb_NO, ne_NP, nl_BE, nl_NL, nn_NO, pa, pl_PL, pt_BR,
+# pt_PT, ro_RO, ru_RU, se, sk_SK, sl_SI, sr_RS, sv_SE, th_TH, to_TO, tr_TR,
+# uk_UA, ur_IN, ur_PK, uz_UZ, vi_VN, zh_CN, zh_TW
+
+# license = {
+# 'default-language': 'en_US',
+# 'licenses': {
+# # For each language, the text of the license. This can be plain text,
+# # RTF (in which case it must start "{\rtf1"), or a path to a file
+# # containing the license text. If you're using RTF,
+# # watch out for Python escaping (or read it from a file).
+# 'English': b'''{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
+# {\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
+# {\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
+# {\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
+# \\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
+# \\deftab720
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
+# \\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\fs36 \\cf2 \\strokec2 What is this?\\
+# \\pard\\pardeftab720\\sa160\\partightenfactor0
+
+# \\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\
+# \\
+# }''',
+# },
+# 'buttons': {
+# # For each language, text for the buttons on the licensing window.
+# #
+# # Default buttons and text are built-in for the following languages:
+# #
+# # English (en_US), German (de_DE), Spanish (es_ES), French (fr_FR),
+# # Italian (it_IT), Japanese (ja_JP), Dutch (nl_NL), Swedish (sv_SE),
+# # Brazilian Portuguese (pt_BR), Simplified Chinese (zh_CN),
+# # Traditional Chinese (zh_TW), Danish (da_DK), Finnish (fi_FI),
+# # Korean (ko_KR), Norwegian (nb_NO)
+# #
+# # You don't need to specify them for those languages; if you fail to
+# # specify them for some other language, English will be used instead.
+
+# 'en_US': (
+# b'English',
+# b'Agree',
+# b'Disagree',
+# b'Print',
+# b'Save',
+# b'If you agree with the terms of this license, press "Agree" to '
+# b'install the software. If you do not agree, press "Disagree".'
+# ),
+# },
+# }
+
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md
similarity index 100%
rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md
rename to .github/pull_request_template.md
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7fd32834..75bdc29d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,9 +25,7 @@ jobs:
submodules: true
- name: Install Qt
- uses: jurplel/install-qt-action@v2
- with:
- modules: qtwebengine
+ uses: jurplel/install-qt-action@v1
# WINDOWS
- name: Cache conan
@@ -79,7 +77,7 @@ jobs:
# LINUX
- name: Install dependencies (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
- run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0
+ run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0 build-essential libgl1-mesa-dev
- name: Build (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..477bf3f5
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+## Unversioned
+- Minor: Emotes in the emote popup are now sorted in the same order as the tab completion (#1549)
+- Bugfix: Fix preview on hover not working when Animated emotes options was disabled (#1546)
+- Bugfix: FFZ custom mod badges no longer scale with the emote scale options (#1602)
+- Settings open faster
+- Dev: Fully remove Twitch Chatroom support
diff --git a/.travis.yml b/_.travis.yml
similarity index 100%
rename from .travis.yml
rename to _.travis.yml
diff --git a/chatterino.pro b/chatterino.pro
index 68058183..243a97ec 100644
--- a/chatterino.pro
+++ b/chatterino.pro
@@ -131,21 +131,16 @@ SOURCES += \
src/controllers/commands/CommandController.cpp \
src/controllers/commands/CommandModel.cpp \
src/controllers/highlights/HighlightBlacklistModel.cpp \
- src/controllers/highlights/HighlightController.cpp \
src/controllers/highlights/HighlightModel.cpp \
src/controllers/highlights/HighlightPhrase.cpp \
src/controllers/highlights/UserHighlightModel.cpp \
- src/controllers/ignores/IgnoreController.cpp \
src/controllers/ignores/IgnoreModel.cpp \
src/controllers/moderationactions/ModerationAction.cpp \
src/controllers/moderationactions/ModerationActionModel.cpp \
- src/controllers/moderationactions/ModerationActions.cpp \
src/controllers/notifications/NotificationController.cpp \
src/controllers/notifications/NotificationModel.cpp \
- src/controllers/pings/PingController.cpp \
- src/controllers/pings/PingModel.cpp \
+ src/controllers/pings/MutedChannelModel.cpp \
src/controllers/taggedusers/TaggedUser.cpp \
- src/controllers/taggedusers/TaggedUsersController.cpp \
src/controllers/taggedusers/TaggedUsersModel.cpp \
src/debug/Benchmark.cpp \
src/main.cpp \
@@ -178,15 +173,14 @@ SOURCES += \
src/providers/irc/IrcConnection2.cpp \
src/providers/irc/IrcServer.cpp \
src/providers/LinkResolver.cpp \
- src/providers/twitch/ChatroomChannel.cpp \
+ src/providers/twitch/api/Helix.cpp \
+ src/providers/twitch/api/Kraken.cpp \
src/providers/twitch/IrcMessageHandler.cpp \
- src/providers/twitch/PartialTwitchUser.cpp \
src/providers/twitch/PubsubActions.cpp \
src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/PubsubHelpers.cpp \
src/providers/twitch/TwitchAccount.cpp \
src/providers/twitch/TwitchAccountManager.cpp \
- src/providers/twitch/TwitchApi.cpp \
src/providers/twitch/TwitchBadge.cpp \
src/providers/twitch/TwitchBadges.cpp \
src/providers/twitch/TwitchChannel.cpp \
@@ -328,7 +322,6 @@ HEADERS += \
src/controllers/commands/CommandModel.hpp \
src/controllers/highlights/HighlightBlacklistModel.hpp \
src/controllers/highlights/HighlightBlacklistUser.hpp \
- src/controllers/highlights/HighlightController.hpp \
src/controllers/highlights/HighlightModel.hpp \
src/controllers/highlights/HighlightPhrase.hpp \
src/controllers/highlights/UserHighlightModel.hpp \
@@ -337,13 +330,10 @@ HEADERS += \
src/controllers/ignores/IgnorePhrase.hpp \
src/controllers/moderationactions/ModerationAction.hpp \
src/controllers/moderationactions/ModerationActionModel.hpp \
- src/controllers/moderationactions/ModerationActions.hpp \
src/controllers/notifications/NotificationController.hpp \
src/controllers/notifications/NotificationModel.hpp \
- src/controllers/pings/PingController.hpp \
- src/controllers/pings/PingModel.hpp \
+ src/controllers/pings/MutedChannelModel.hpp \
src/controllers/taggedusers/TaggedUser.hpp \
- src/controllers/taggedusers/TaggedUsersController.hpp \
src/controllers/taggedusers/TaggedUsersModel.hpp \
src/debug/AssertInGuiThread.hpp \
src/debug/Benchmark.hpp \
@@ -383,16 +373,15 @@ HEADERS += \
src/providers/irc/IrcConnection2.hpp \
src/providers/irc/IrcServer.hpp \
src/providers/LinkResolver.hpp \
- src/providers/twitch/ChatroomChannel.hpp \
+ src/providers/twitch/api/Helix.hpp \
+ src/providers/twitch/api/Kraken.hpp \
src/providers/twitch/EmoteValue.hpp \
src/providers/twitch/IrcMessageHandler.hpp \
- src/providers/twitch/PartialTwitchUser.hpp \
src/providers/twitch/PubsubActions.hpp \
src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/PubsubHelpers.hpp \
src/providers/twitch/TwitchAccount.hpp \
src/providers/twitch/TwitchAccountManager.hpp \
- src/providers/twitch/TwitchApi.hpp \
src/providers/twitch/TwitchBadge.hpp \
src/providers/twitch/TwitchBadges.hpp \
src/providers/twitch/TwitchChannel.hpp \
@@ -437,6 +426,7 @@ HEADERS += \
src/util/LayoutCreator.hpp \
src/util/LayoutHelper.hpp \
src/util/Overloaded.hpp \
+ src/util/PersistSignalVector.hpp \
src/util/PostToThread.hpp \
src/util/QObjectRef.hpp \
src/util/QStringHash.hpp \
@@ -564,3 +554,11 @@ git_hash = $$str_member($$git_commit, 0, 8)
DEFINES += CHATTERINO_GIT_COMMIT=\\\"$$git_commit\\\"
DEFINES += CHATTERINO_GIT_RELEASE=\\\"$$git_release\\\"
DEFINES += CHATTERINO_GIT_HASH=\\\"$$git_hash\\\"
+
+CONFIG(debug, debug|release) {
+ message("Building Chatterino2 DEBUG")
+} else {
+ message("Building Chatterino2 RELEASE")
+}
+
+message("Injected git values: $$git_commit ($$git_release) $$git_hash")
diff --git a/resources/contributors.txt b/resources/contributors.txt
index ea0644a7..cf94931e 100644
--- a/resources/contributors.txt
+++ b/resources/contributors.txt
@@ -29,6 +29,7 @@ TranRed | https://github.com/TranRed | | Contributor
RAnders00 | https://github.com/RAnders00 | | Contributor
YungLPR | https://github.com/leon-richardt | | Contributor
Mm2PL | https://github.com/mm2pl | | Contributor
+gempir | https://github.com/gempir | | Contributor
# If you are a contributor add yourself above this line
Defman21 | https://github.com/Defman21 | | Documentation
diff --git a/resources/qss/settings.qss b/resources/qss/settings.qss
index 590dff5f..a3172fd6 100644
--- a/resources/qss/settings.qss
+++ b/resources/qss/settings.qss
@@ -39,7 +39,6 @@ chatterino--TitleLabel {
font-family: "Segoe UI light";
font-size: 24px;
color: #4FC3F7;
- margin-top: 16px;
}
chatterino--DescriptionLabel {
diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc
index dbb639bb..4fe4cf72 100644
--- a/resources/resources_autogenerated.qrc
+++ b/resources/resources_autogenerated.qrc
@@ -28,8 +28,9 @@
buttons/unmod.png
buttons/update.png
buttons/updateError.png
- com.chatterino.chatterino.desktop
chatterino.icns
+ com.chatterino.chatterino.appdata.xml
+ com.chatterino.chatterino.desktop
contributors.txt
emoji.json
emojidata.txt
@@ -50,6 +51,12 @@
licenses/websocketpp.txt
pajaDank.png
qss/settings.qss
+ scrolling/downScroll.png
+ scrolling/downScroll.svg
+ scrolling/neutralScroll.png
+ scrolling/neutralScroll.svg
+ scrolling/upScroll.png
+ scrolling/upScroll.svg
settings/about.svg
settings/aboutlogo.png
settings/accounts.svg
diff --git a/resources/scrolling/downScroll.png b/resources/scrolling/downScroll.png
new file mode 100644
index 00000000..cc46bb5c
Binary files /dev/null and b/resources/scrolling/downScroll.png differ
diff --git a/resources/scrolling/downScroll.svg b/resources/scrolling/downScroll.svg
new file mode 100644
index 00000000..d2e8992a
--- /dev/null
+++ b/resources/scrolling/downScroll.svg
@@ -0,0 +1,96 @@
+
+
+
+
diff --git a/resources/scrolling/neutralScroll.png b/resources/scrolling/neutralScroll.png
new file mode 100644
index 00000000..99ad1e7f
Binary files /dev/null and b/resources/scrolling/neutralScroll.png differ
diff --git a/resources/scrolling/neutralScroll.svg b/resources/scrolling/neutralScroll.svg
new file mode 100644
index 00000000..6ad76c8c
--- /dev/null
+++ b/resources/scrolling/neutralScroll.svg
@@ -0,0 +1,122 @@
+
+
+
+
diff --git a/resources/scrolling/upScroll.png b/resources/scrolling/upScroll.png
new file mode 100644
index 00000000..43364297
Binary files /dev/null and b/resources/scrolling/upScroll.png differ
diff --git a/resources/scrolling/upScroll.svg b/resources/scrolling/upScroll.svg
new file mode 100644
index 00000000..d25c3f10
--- /dev/null
+++ b/resources/scrolling/upScroll.svg
@@ -0,0 +1,96 @@
+
+
+
+
diff --git a/src/Application.cpp b/src/Application.cpp
index fe0aab34..6934ebbd 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -5,12 +5,8 @@
#include "common/Args.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
-#include "controllers/highlights/HighlightController.hpp"
#include "controllers/ignores/IgnoreController.hpp"
-#include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/notifications/NotificationController.hpp"
-#include "controllers/pings/PingController.hpp"
-#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
@@ -54,16 +50,10 @@ Application::Application(Settings &_settings, Paths &_paths)
, accounts(&this->emplace())
, commands(&this->emplace())
- , highlights(&this->emplace())
, notifications(&this->emplace())
- , pings(&this->emplace())
- , ignores(&this->emplace())
- , taggedUsers(&this->emplace())
- , moderationActions(&this->emplace())
, twitch2(&this->emplace())
, chatterinoBadges(&this->emplace())
, logging(&this->emplace())
-
{
this->instance = this;
@@ -115,9 +105,6 @@ void Application::initialize(Settings &settings, Paths &paths)
this->initNm(paths);
this->initPubsub();
-
- this->moderationActions->items.delayedItemsChanged.connect(
- [this] { this->windows->forceLayoutChannelViews(); });
}
int Application::run(QApplication &qtApp)
@@ -130,6 +117,13 @@ int Application::run(QApplication &qtApp)
getSettings()->betaUpdates.connect(
[] { Updates::instance().checkForUpdates(); }, false);
+ getSettings()->moderationActions.delayedItemsChanged.connect(
+ [this] { this->windows->forceLayoutChannelViews(); });
+
+ getSettings()->highlightedMessages.delayedItemsChanged.connect(
+ [this] { this->windows->forceLayoutChannelViews(); });
+ getSettings()->highlightedUsers.delayedItemsChanged.connect(
+ [this] { this->windows->forceLayoutChannelViews(); });
return qtApp.exec();
}
@@ -156,14 +150,6 @@ void Application::initNm(Paths &paths)
void Application::initPubsub()
{
- this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
- qDebug() << "WHISPER SENT LOL"; //
- });
-
- this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
- qDebug() << "WHISPER RECEIVED LOL"; //
- });
-
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
[this](const auto &action) {
auto chan =
diff --git a/src/Application.hpp b/src/Application.hpp
index fac8ec0f..0c0e54bc 100644
--- a/src/Application.hpp
+++ b/src/Application.hpp
@@ -3,6 +3,7 @@
#include
#include
+#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "singletons/NativeMessaging.hpp"
@@ -12,13 +13,8 @@ class TwitchIrcServer;
class PubSub;
class CommandController;
-class HighlightController;
-class IgnoreController;
-class TaggedUsersController;
class AccountController;
-class ModerationActions;
class NotificationController;
-class PingController;
class Theme;
class WindowManager;
@@ -58,12 +54,7 @@ public:
AccountController *const accounts{};
CommandController *const commands{};
- HighlightController *const highlights{};
NotificationController *const notifications{};
- PingController *const pings{};
- IgnoreController *const ignores{};
- TaggedUsersController *const taggedUsers{};
- ModerationActions *const moderationActions{};
TwitchIrcServer *const twitch2{};
ChatterinoBadges *const chatterinoBadges{};
diff --git a/src/BaseTheme.cpp b/src/BaseTheme.cpp
index 99ddddb2..306f13f5 100644
--- a/src/BaseTheme.cpp
+++ b/src/BaseTheme.cpp
@@ -159,21 +159,6 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
this->messages.backgrounds.regular = getColor(0, sat, 1);
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
- if (isLight_)
- {
- this->messages.backgrounds.highlighted =
- blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
- }
- else
- {
- // REMOVED
- // this->messages.backgrounds.highlighted =
- // QColor(getSettings()->highlightColor);
- }
-
- this->messages.backgrounds.subscription =
- blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
-
// this->messages.backgrounds.resub
// this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6);
diff --git a/src/BaseTheme.hpp b/src/BaseTheme.hpp
index d9980813..36e14c07 100644
--- a/src/BaseTheme.hpp
+++ b/src/BaseTheme.hpp
@@ -64,8 +64,6 @@ public:
struct {
QColor regular;
QColor alternate;
- QColor highlighted;
- QColor subscription;
// QColor whisper;
} backgrounds;
diff --git a/src/RunGui.cpp b/src/RunGui.cpp
index 3088b6da..e025d7f2 100644
--- a/src/RunGui.cpp
+++ b/src/RunGui.cpp
@@ -154,11 +154,6 @@ namespace {
toBeRemoved << info.absoluteFilePath();
}
}
-
- for (auto &&path : toBeRemoved)
- {
- qDebug() << path << QFile(path).remove();
- }
}
} // namespace
diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp
index e7aab5d5..b0e5e6ce 100644
--- a/src/autogenerated/ResourcesAutogen.cpp
+++ b/src/autogenerated/ResourcesAutogen.cpp
@@ -30,6 +30,9 @@ Resources2::Resources2()
this->error = QPixmap(":/error.png");
this->icon = QPixmap(":/icon.png");
this->pajaDank = QPixmap(":/pajaDank.png");
+ this->scrolling.downScroll = QPixmap(":/scrolling/downScroll.png");
+ this->scrolling.neutralScroll = QPixmap(":/scrolling/neutralScroll.png");
+ this->scrolling.upScroll = QPixmap(":/scrolling/upScroll.png");
this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png");
this->split.down = QPixmap(":/split/down.png");
this->split.left = QPixmap(":/split/left.png");
@@ -50,4 +53,4 @@ Resources2::Resources2()
this->twitch.vip = QPixmap(":/twitch/vip.png");
}
-} // namespace chatterino
+} // namespace chatterino
\ No newline at end of file
diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp
index cb342363..cbd6c0bf 100644
--- a/src/autogenerated/ResourcesAutogen.hpp
+++ b/src/autogenerated/ResourcesAutogen.hpp
@@ -1,5 +1,4 @@
#include
-
#include "common/Singleton.hpp"
namespace chatterino {
@@ -39,6 +38,11 @@ public:
QPixmap error;
QPixmap icon;
QPixmap pajaDank;
+ struct {
+ QPixmap downScroll;
+ QPixmap neutralScroll;
+ QPixmap upScroll;
+ } scrolling;
struct {
QPixmap aboutlogo;
} settings;
@@ -65,4 +69,4 @@ public:
} twitch;
};
-} // namespace chatterino
+} // namespace chatterino
\ No newline at end of file
diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp
index 4866e8a2..f3d368da 100644
--- a/src/common/CompletionModel.cpp
+++ b/src/common/CompletionModel.cpp
@@ -38,13 +38,7 @@ bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
return this->isEmote();
}
- // try comparing insensitively, if they are the same then senstively
- // (fixes order of LuL and LUL)
- int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
- if (k == 0)
- return this->string > that.string;
-
- return k < 0;
+ return CompletionModel::compareStrings(this->string, that.string);
}
//
@@ -133,7 +127,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
TaggedString::Type::Username);
}
}
- else
+ else if (!getSettings()->userCompletionOnlyWithAt)
{
for (const auto &name :
usernames->subrange(Prefix(usernamePrefix)))
@@ -191,4 +185,15 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
}
}
+bool CompletionModel::compareStrings(const QString &a, const QString &b)
+{
+ // try comparing insensitively, if they are the same then senstively
+ // (fixes order of LuL and LUL)
+ int k = QString::compare(a, b, Qt::CaseInsensitive);
+ if (k == 0)
+ return a > b;
+
+ return k < 0;
+}
+
} // namespace chatterino
diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp
index 321929e7..2ce37439 100644
--- a/src/common/CompletionModel.hpp
+++ b/src/common/CompletionModel.hpp
@@ -49,6 +49,8 @@ public:
void refresh(const QString &prefix, bool isFirstWord = false);
+ static bool compareStrings(const QString &a, const QString &b);
+
private:
TaggedString createUser(const QString &str);
diff --git a/src/common/SignalVector.hpp b/src/common/SignalVector.hpp
index 2995cbba..33bd8254 100644
--- a/src/common/SignalVector.hpp
+++ b/src/common/SignalVector.hpp
@@ -4,244 +4,171 @@
#include
#include
#include
-#include
#include
#include "debug/AssertInGuiThread.hpp"
namespace chatterino {
-template
-struct SignalVectorItemArgs {
- const TVectorItem &item;
+template
+struct SignalVectorItemEvent {
+ const T &item;
int index;
void *caller;
};
-template
-class ReadOnlySignalVector : boost::noncopyable
+template
+class SignalVector : boost::noncopyable
{
- using VecIt = typename std::vector::iterator;
-
public:
- struct Iterator
- : public std::iterator {
- Iterator(VecIt &&it, std::shared_mutex &mutex)
- : it_(std::move(it))
- , lock_(mutex)
- , mutex_(mutex)
- {
- }
+ pajlada::Signals::Signal> itemInserted;
+ pajlada::Signals::Signal> itemRemoved;
+ pajlada::Signals::NoArgSignal delayedItemsChanged;
- Iterator(const Iterator &other)
- : it_(other.it_)
- , lock_(other.mutex_)
- , mutex_(other.mutex_)
- {
- }
-
- Iterator &operator=(const Iterator &other)
- {
- this->lock_ = std::shared_lock(other.mutex_.get());
- this->mutex_ = other.mutex_;
-
- return *this;
- }
-
- TVectorItem &operator*()
- {
- return it_.operator*();
- }
-
- Iterator &operator++()
- {
- ++this->it_;
- return *this;
- }
-
- bool operator==(const Iterator &other)
- {
- return this->it_ == other.it_;
- }
-
- bool operator!=(const Iterator &other)
- {
- return this->it_ != other.it_;
- }
-
- auto operator-(const Iterator &other)
- {
- return this->it_ - other.it_;
- }
-
- private:
- VecIt it_;
- std::shared_lock lock_;
- std::reference_wrapper mutex_;
- };
-
- ReadOnlySignalVector()
+ SignalVector()
+ : readOnly_(new std::vector())
{
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
[this] { this->delayedItemsChanged.invoke(); });
this->itemsChangedTimer_.setInterval(100);
this->itemsChangedTimer_.setSingleShot(true);
}
- virtual ~ReadOnlySignalVector() = default;
- pajlada::Signals::Signal> itemInserted;
- pajlada::Signals::Signal> itemRemoved;
- pajlada::Signals::NoArgSignal delayedItemsChanged;
-
- Iterator begin() const
+ SignalVector(std::function &&compare)
+ : SignalVector()
{
- return Iterator(
- const_cast &>(this->vector_).begin(),
- this->mutex_);
+ itemCompare_ = std::move(compare);
}
- Iterator end() const
+ virtual bool isSorted() const
{
- return Iterator(
- const_cast &>(this->vector_).end(),
- this->mutex_);
+ return bool(this->itemCompare_);
}
- bool empty() const
+ /// A read-only version of the vector which can be used concurrently.
+ std::shared_ptr> readOnly()
{
- std::shared_lock lock(this->mutex_);
-
- return this->vector_.empty();
+ return this->readOnly_;
}
- const std::vector &getVector() const
+ /// This may only be called from the GUI thread.
+ ///
+ /// @param item
+ /// Item to be inserted.
+ /// @param proposedIndex
+ /// Index to insert at. `-1` will append at the end.
+ /// Will be ignored if the vector is sorted.
+ /// @param caller
+ /// Caller id which will be passed in the itemInserted and itemRemoved
+ /// signals.
+ int insert(const T &item, int index = -1, void *caller = nullptr)
{
assertInGuiThread();
- return this->vector_;
+ if (this->isSorted())
+ {
+ auto it = std::lower_bound(this->items_.begin(), this->items_.end(),
+ item, this->itemCompare_);
+ index = it - this->items_.begin();
+ this->items_.insert(it, item);
+ }
+ else
+ {
+ if (index == -1)
+ index = this->items_.size();
+ else
+ assert(index >= 0 && index <= this->items_.size());
+
+ this->items_.insert(this->items_.begin() + index, item);
+ }
+
+ SignalVectorItemEvent args{item, index, caller};
+ this->itemInserted.invoke(args);
+ this->itemsChanged_();
+
+ return index;
}
- std::vector cloneVector() const
+ /// This may only be called from the GUI thread.
+ ///
+ /// @param item
+ /// Item to be appended.
+ /// @param caller
+ /// Caller id which will be passed in the itemInserted and itemRemoved
+ /// signals.
+ int append(const T &item, void *caller = nullptr)
{
- std::shared_lock lock(this->mutex_);
-
- return this->vector_;
+ return this->insert(item, -1, caller);
}
- void invokeDelayedItemsChanged()
+ void removeAt(int index, void *caller = nullptr)
+ {
+ assertInGuiThread();
+ assert(index >= 0 && index < int(this->items_.size()));
+
+ T item = this->items_[index];
+ this->items_.erase(this->items_.begin() + index);
+
+ SignalVectorItemEvent args{item, index, caller};
+ this->itemRemoved.invoke(args);
+
+ this->itemsChanged_();
+ }
+
+ const std::vector &raw() const
{
assertInGuiThread();
+ return this->items_;
+ }
+
+ [[deprecated]] std::vector cloneVector()
+ {
+ return *this->readOnly();
+ }
+
+ // mirror vector functions
+ auto begin() const
+ {
+ assertInGuiThread();
+ return this->items_.begin();
+ }
+
+ auto end() const
+ {
+ assertInGuiThread();
+ return this->items_.end();
+ }
+
+ decltype(auto) operator[](size_t index)
+ {
+ assertInGuiThread();
+ return this->items[index];
+ }
+
+ auto empty()
+ {
+ assertInGuiThread();
+ return this->items_.empty();
+ }
+
+private:
+ void itemsChanged_()
+ {
+ // emit delayed event
if (!this->itemsChangedTimer_.isActive())
{
this->itemsChangedTimer_.start();
}
+
+ // update concurrent version
+ this->readOnly_ = std::make_shared>(this->items_);
}
- virtual bool isSorted() const = 0;
-
-protected:
- std::vector vector_;
+ std::vector items_;
+ std::shared_ptr> readOnly_;
QTimer itemsChangedTimer_;
- mutable std::shared_mutex mutex_;
-};
-
-template
-class BaseSignalVector : public ReadOnlySignalVector
-{
-public:
- // returns the actual index of the inserted item
- virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
- void *caller = nullptr) = 0;
-
- void removeItem(int index, void *caller = nullptr)
- {
- assertInGuiThread();
- std::unique_lock lock(this->mutex_);
-
- assert(index >= 0 && index < int(this->vector_.size()));
-
- TVectorItem item = this->vector_[index];
-
- this->vector_.erase(this->vector_.begin() + index);
- lock.unlock(); // manual unlock
-
- SignalVectorItemArgs args{item, index, caller};
- this->itemRemoved.invoke(args);
-
- this->invokeDelayedItemsChanged();
- }
-
- int appendItem(const TVectorItem &item, void *caller = nullptr)
- {
- return this->insertItem(item, -1, caller);
- }
-};
-
-template
-class UnsortedSignalVector : public BaseSignalVector
-{
-public:
- virtual int insertItem(const TVectorItem &item, int index = -1,
- void *caller = nullptr) override
- {
- assertInGuiThread();
-
- {
- std::unique_lock lock(this->mutex_);
- if (index == -1)
- {
- index = this->vector_.size();
- }
- else
- {
- assert(index >= 0 && index <= this->vector_.size());
- }
-
- this->vector_.insert(this->vector_.begin() + index, item);
- }
-
- SignalVectorItemArgs args{item, index, caller};
- this->itemInserted.invoke(args);
- this->invokeDelayedItemsChanged();
- return index;
- }
-
- virtual bool isSorted() const override
- {
- return false;
- }
-};
-
-template
-class SortedSignalVector : public BaseSignalVector
-{
-public:
- virtual int insertItem(const TVectorItem &item, int = -1,
- void *caller = nullptr) override
- {
- assertInGuiThread();
- int index = -1;
-
- {
- std::unique_lock lock(this->mutex_);
-
- auto it = std::lower_bound(this->vector_.begin(),
- this->vector_.end(), item, Compare{});
- index = it - this->vector_.begin();
- this->vector_.insert(it, item);
- }
-
- SignalVectorItemArgs args{item, index, caller};
- this->itemInserted.invoke(args);
- this->invokeDelayedItemsChanged();
- return index;
- }
-
- virtual bool isSorted() const override
- {
- return true;
- }
+ std::function itemCompare_;
};
} // namespace chatterino
diff --git a/src/common/SignalVectorModel.hpp b/src/common/SignalVectorModel.hpp
index adea7884..76bc14b2 100644
--- a/src/common/SignalVectorModel.hpp
+++ b/src/common/SignalVectorModel.hpp
@@ -25,11 +25,11 @@ public:
}
}
- void init(BaseSignalVector *vec)
+ void initialize(SignalVector *vec)
{
this->vector_ = vec;
- auto insert = [this](const SignalVectorItemArgs &args) {
+ auto insert = [this](const SignalVectorItemEvent &args) {
if (args.caller == this)
{
return;
@@ -52,9 +52,9 @@ public:
};
int i = 0;
- for (const TVectorItem &item : vec->getVector())
+ for (const TVectorItem &item : vec->raw())
{
- SignalVectorItemArgs args{item, i++, 0};
+ SignalVectorItemEvent args{item, i++, 0};
insert(args);
}
@@ -89,6 +89,12 @@ public:
this->afterInit();
}
+ SignalVectorModel *initialized(SignalVector *vec)
+ {
+ this->initialize(vec);
+ return this;
+ }
+
virtual ~SignalVectorModel()
{
for (Row &row : this->rows_)
@@ -147,12 +153,12 @@ public:
else
{
int vecRow = this->getVectorIndexFromModelIndex(row);
- this->vector_->removeItem(vecRow, this);
+ this->vector_->removeAt(vecRow, this);
assert(this->rows_[row].original);
TVectorItem item = this->getItemFromRow(
this->rows_[row].items, this->rows_[row].original.get());
- this->vector_->insertItem(item, vecRow, this);
+ this->vector_->insert(item, vecRow, this);
}
return true;
@@ -219,7 +225,7 @@ public:
void deleteRow(int row)
{
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
- this->vector_->removeItem(signalVectorRow);
+ this->vector_->removeAt(signalVectorRow);
}
bool removeRows(int row, int count, const QModelIndex &parent) override
@@ -234,11 +240,71 @@ public:
assert(row >= 0 && row < this->rows_.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
- this->vector_->removeItem(signalVectorRow);
+ this->vector_->removeAt(signalVectorRow);
return true;
}
+ QStringList mimeTypes() const override
+ {
+ return {"chatterino_row_id"};
+ }
+
+ QMimeData *mimeData(const QModelIndexList &list) const
+ {
+ if (list.length() == 1)
+ {
+ return nullptr;
+ }
+
+ // Check if all indices are in the same row -> single row selected
+ for (auto &&x : list)
+ {
+ if (x.row() != list.first().row())
+ return nullptr;
+ }
+
+ auto data = new QMimeData;
+ data->setData("chatterino_row_id", QByteArray::number(list[0].row()));
+ return data;
+ }
+
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int /*row*/,
+ int /*column*/, const QModelIndex &parent) override
+ {
+ if (data->hasFormat("chatterino_row_id") &&
+ action & (Qt::DropAction::MoveAction | Qt::DropAction::CopyAction))
+ {
+ int from = data->data("chatterino_row_id").toInt();
+ int to = parent.row();
+
+ if (from < 0 || from > this->vector_->raw().size() || to < 0 ||
+ to > this->vector_->raw().size())
+ {
+ return false;
+ }
+
+ if (from != to)
+ {
+ auto item = this->vector_->raw()[from];
+ this->vector_->removeAt(from);
+ this->vector_->insert(item, to);
+ }
+
+ // We return false since we remove items ourselves.
+ return false;
+ }
+
+ return false;
+ }
+
+ Qt::DropActions supportedDropActions() const override
+ {
+ return this->vector_->isSorted()
+ ? Qt::DropActions()
+ : Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
+ }
+
protected:
virtual void afterInit()
{
@@ -326,7 +392,7 @@ protected:
private:
std::vector> headerData_;
- BaseSignalVector *vector_;
+ SignalVector *vector_;
std::vector rows_;
int columnCount_;
diff --git a/src/controllers/accounts/AccountController.cpp b/src/controllers/accounts/AccountController.cpp
index cdaa5328..2692b05a 100644
--- a/src/controllers/accounts/AccountController.cpp
+++ b/src/controllers/accounts/AccountController.cpp
@@ -7,20 +7,20 @@
namespace chatterino {
AccountController::AccountController()
+ : accounts_(SharedPtrElementLess{})
{
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
- this->accounts_.insertItem(
- std::dynamic_pointer_cast(args.item));
+ this->accounts_.insert(std::dynamic_pointer_cast(args.item));
});
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this)
{
- auto &accs = this->twitch.accounts.getVector();
+ auto &accs = this->twitch.accounts.raw();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
- this->accounts_.removeItem(it - accs.begin(), this);
+ this->accounts_.removeAt(it - accs.begin(), this);
}
});
@@ -30,10 +30,10 @@ AccountController::AccountController()
case ProviderId::Twitch: {
if (args.caller != this)
{
- auto accs = this->twitch.accounts.cloneVector();
+ auto &&accs = this->twitch.accounts;
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
- this->twitch.accounts.removeItem(it - accs.begin(), this);
+ this->twitch.accounts.removeAt(it - accs.begin(), this);
}
}
break;
@@ -50,7 +50,7 @@ AccountModel *AccountController::createModel(QObject *parent)
{
AccountModel *model = new AccountModel(parent);
- model->init(&this->accounts_);
+ model->initialize(&this->accounts_);
return model;
}
diff --git a/src/controllers/accounts/AccountController.hpp b/src/controllers/accounts/AccountController.hpp
index e297b9e4..e1ef3c70 100644
--- a/src/controllers/accounts/AccountController.hpp
+++ b/src/controllers/accounts/AccountController.hpp
@@ -27,8 +27,7 @@ public:
TwitchAccountManager twitch;
private:
- SortedSignalVector, SharedPtrElementLess>
- accounts_;
+ SignalVector> accounts_;
};
} // namespace chatterino
diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp
index a6150cfa..fa8a08e9 100644
--- a/src/controllers/commands/CommandController.cpp
+++ b/src/controllers/commands/CommandController.cpp
@@ -8,9 +8,9 @@
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
-#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
+#include "providers/twitch/api/Helix.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
@@ -212,7 +212,7 @@ void CommandController::initialize(Settings &, Paths &paths)
// Update the setting when the vector of commands has been updated (most
// likely from the settings dialog)
this->items_.delayedItemsChanged.connect([this] { //
- this->commandsSetting_->setValue(this->items_.getVector());
+ this->commandsSetting_->setValue(this->items_.raw());
});
// Load commands from commands.json
@@ -222,7 +222,7 @@ void CommandController::initialize(Settings &, Paths &paths)
// of commands)
for (const auto &command : this->commandsSetting_->getValue())
{
- this->items_.appendItem(command);
+ this->items_.append(command);
}
}
@@ -234,7 +234,7 @@ void CommandController::save()
CommandModel *CommandController::createModel(QObject *parent)
{
CommandModel *model = new CommandModel(parent);
- model->init(&this->items_);
+ model->initialize(&this->items_);
return model;
}
@@ -245,8 +245,6 @@ QString CommandController::execCommand(const QString &textNoEmoji,
QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
QStringList words = text.split(' ', QString::SkipEmptyParts);
- std::lock_guard lock(this->mutex_);
-
if (words.length() == 0)
{
return text;
@@ -366,18 +364,17 @@ QString CommandController::execCommand(const QString &textNoEmoji,
return "";
}
- TwitchApi::findUserId(
- target, [user, channel, target](QString userId) {
- if (userId.isEmpty())
- {
- channel->addMessage(makeSystemMessage(
- "User " + target + " could not be followed!"));
- return;
- }
- user->followUser(userId, [channel, target]() {
+ getHelix()->getUserByName(
+ target,
+ [user, channel, target](const auto &targetUser) {
+ user->followUser(targetUser.id, [channel, target]() {
channel->addMessage(makeSystemMessage(
"You successfully followed " + target));
});
+ },
+ [channel, target] {
+ channel->addMessage(makeSystemMessage(
+ "User " + target + " could not be followed!"));
});
return "";
@@ -402,18 +399,17 @@ QString CommandController::execCommand(const QString &textNoEmoji,
return "";
}
- TwitchApi::findUserId(
- target, [user, channel, target](QString userId) {
- if (userId.isEmpty())
- {
- channel->addMessage(makeSystemMessage(
- "User " + target + " could not be followed!"));
- return;
- }
- user->unfollowUser(userId, [channel, target]() {
+ getHelix()->getUserByName(
+ target,
+ [user, channel, target](const auto &targetUser) {
+ user->unfollowUser(targetUser.id, [channel, target]() {
channel->addMessage(makeSystemMessage(
"You successfully unfollowed " + target));
});
+ },
+ [channel, target] {
+ channel->addMessage(makeSystemMessage(
+ "User " + target + " could not be followed!"));
});
return "";
diff --git a/src/controllers/commands/CommandController.hpp b/src/controllers/commands/CommandController.hpp
index 938fe0f3..ebc57d8c 100644
--- a/src/controllers/commands/CommandController.hpp
+++ b/src/controllers/commands/CommandController.hpp
@@ -22,7 +22,7 @@ class CommandModel;
class CommandController final : public Singleton
{
public:
- UnsortedSignalVector items_;
+ SignalVector items_;
QString execCommand(const QString &text, std::shared_ptr channel,
bool dryRun);
@@ -39,8 +39,6 @@ private:
QMap commandsMap_;
int maxSpaces_ = 0;
- std::mutex mutex_;
-
std::shared_ptr sm_;
// Because the setting manager is not initialized until the initialize
// function is called (and not in the constructor), we have to
diff --git a/src/controllers/commands/CommandModel.cpp b/src/controllers/commands/CommandModel.cpp
index 4a55907d..d12a81c6 100644
--- a/src/controllers/commands/CommandModel.cpp
+++ b/src/controllers/commands/CommandModel.cpp
@@ -1,5 +1,7 @@
#include "CommandModel.hpp"
+#include "util/StandardItemHelper.hpp"
+
namespace chatterino {
// commandmodel
@@ -20,12 +22,8 @@ Command CommandModel::getItemFromRow(std::vector &row,
void CommandModel::getRowFromItem(const Command &item,
std::vector &row)
{
- row[0]->setData(item.name, Qt::DisplayRole);
- row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
- Qt::ItemIsEditable);
- row[1]->setData(item.func, Qt::DisplayRole);
- row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
- Qt::ItemIsEditable);
+ setStringItem(row[0], item.name);
+ setStringItem(row[1], item.func);
}
} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightBlacklistModel.hpp b/src/controllers/highlights/HighlightBlacklistModel.hpp
index c3420b06..d4acc474 100644
--- a/src/controllers/highlights/HighlightBlacklistModel.hpp
+++ b/src/controllers/highlights/HighlightBlacklistModel.hpp
@@ -11,9 +11,9 @@ class HighlightController;
class HighlightBlacklistModel : public SignalVectorModel
{
+public:
explicit HighlightBlacklistModel(QObject *parent);
-public:
enum Column {
Pattern = 0,
UseRegex = 1,
@@ -28,8 +28,6 @@ protected:
// turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightBlacklistUser &item,
std::vector &row) override;
-
- friend class HighlightController;
};
} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp
deleted file mode 100644
index d030f77b..00000000
--- a/src/controllers/highlights/HighlightController.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-#include "HighlightController.hpp"
-
-#include "Application.hpp"
-#include "controllers/highlights/HighlightBlacklistModel.hpp"
-#include "controllers/highlights/HighlightModel.hpp"
-#include "controllers/highlights/UserHighlightModel.hpp"
-#include "widgets/dialogs/NotificationPopup.hpp"
-
-namespace chatterino {
-
-HighlightController::HighlightController()
-{
-}
-
-void HighlightController::initialize(Settings &settings, Paths &paths)
-{
- assert(!this->initialized_);
- this->initialized_ = true;
-
- for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
- {
- this->phrases.appendItem(phrase);
- }
-
- this->phrases.delayedItemsChanged.connect([this] { //
- this->highlightsSetting_.setValue(this->phrases.getVector());
- });
-
- for (const HighlightBlacklistUser &blacklistedUser :
- this->blacklistSetting_.getValue())
- {
- this->blacklistedUsers.appendItem(blacklistedUser);
- }
-
- this->blacklistedUsers.delayedItemsChanged.connect([this] {
- this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
- });
-
- for (const HighlightPhrase &user : this->userSetting_.getValue())
- {
- this->highlightedUsers.appendItem(user);
- }
-
- this->highlightedUsers.delayedItemsChanged.connect([this] { //
- this->userSetting_.setValue(this->highlightedUsers.getVector());
- });
-}
-
-HighlightModel *HighlightController::createModel(QObject *parent)
-{
- HighlightModel *model = new HighlightModel(parent);
- model->init(&this->phrases);
-
- return model;
-}
-
-UserHighlightModel *HighlightController::createUserModel(QObject *parent)
-{
- auto *model = new UserHighlightModel(parent);
- model->init(&this->highlightedUsers);
-
- return model;
-}
-
-bool HighlightController::isHighlightedUser(const QString &username)
-{
- const auto &userItems = this->highlightedUsers;
- for (const auto &highlightedUser : userItems)
- {
- if (highlightedUser.isMatch(username))
- {
- return true;
- }
- }
-
- return false;
-}
-
-HighlightBlacklistModel *HighlightController::createBlacklistModel(
- QObject *parent)
-{
- auto *model = new HighlightBlacklistModel(parent);
- model->init(&this->blacklistedUsers);
-
- return model;
-}
-
-bool HighlightController::blacklistContains(const QString &username)
-{
- for (const auto &blacklistedUser : this->blacklistedUsers)
- {
- if (blacklistedUser.isMatch(username))
- {
- return true;
- }
- }
-
- return false;
-}
-
-void HighlightController::addHighlight(const MessagePtr &msg)
-{
- // static NotificationPopup popup;
-
- // popup.updatePosition();
- // popup.addMessage(msg);
- // popup.show();
-}
-
-} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightController.hpp b/src/controllers/highlights/HighlightController.hpp
deleted file mode 100644
index faa4683c..00000000
--- a/src/controllers/highlights/HighlightController.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-#include "common/ChatterinoSetting.hpp"
-#include "common/SignalVector.hpp"
-#include "common/Singleton.hpp"
-#include "controllers/highlights/HighlightBlacklistUser.hpp"
-#include "controllers/highlights/HighlightPhrase.hpp"
-
-namespace chatterino {
-
-struct Message;
-using MessagePtr = std::shared_ptr;
-
-class Settings;
-class Paths;
-
-class UserHighlightModel;
-class HighlightModel;
-class HighlightBlacklistModel;
-
-class HighlightController final : public Singleton
-{
-public:
- HighlightController();
-
- virtual void initialize(Settings &settings, Paths &paths) override;
-
- UnsortedSignalVector phrases;
- UnsortedSignalVector blacklistedUsers;
- UnsortedSignalVector highlightedUsers;
-
- HighlightModel *createModel(QObject *parent);
- HighlightBlacklistModel *createBlacklistModel(QObject *parent);
- UserHighlightModel *createUserModel(QObject *parent);
-
- bool isHighlightedUser(const QString &username);
- bool blacklistContains(const QString &username);
-
- void addHighlight(const MessagePtr &msg);
-
-private:
- bool initialized_ = false;
-
- ChatterinoSetting> highlightsSetting_ = {
- "/highlighting/highlights"};
- ChatterinoSetting> blacklistSetting_ = {
- "/highlighting/blacklist"};
- ChatterinoSetting> userSetting_ = {
- "/highlighting/users"};
-};
-
-} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightModel.cpp b/src/controllers/highlights/HighlightModel.cpp
index 05f37bf7..e1b6e410 100644
--- a/src/controllers/highlights/HighlightModel.cpp
+++ b/src/controllers/highlights/HighlightModel.cpp
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "singletons/Settings.hpp"
+#include "singletons/WindowManager.hpp"
#include "util/StandardItemHelper.hpp"
namespace chatterino {
@@ -63,10 +64,10 @@ void HighlightModel::afterInit()
usernameRow[Column::CaseSensitive]->setFlags(0);
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
- setFilePathItem(usernameRow[Column::SoundPath], selfSound);
+ setFilePathItem(usernameRow[Column::SoundPath], selfSound, false);
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
- setColorItem(usernameRow[Column::Color], *selfColor);
+ setColorItem(usernameRow[Column::Color], *selfColor, false);
this->insertCustomRow(usernameRow, 0);
@@ -86,12 +87,13 @@ void HighlightModel::afterInit()
QUrl whisperSound =
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
- setFilePathItem(whisperRow[Column::SoundPath], whisperSound);
+ setFilePathItem(whisperRow[Column::SoundPath], whisperSound, false);
- auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
- setColorItem(whisperRow[Column::Color], *whisperColor);
+ // auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
+ // setColorItem(whisperRow[Column::Color], *whisperColor, false);
+ whisperRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags);
- this->insertCustomRow(whisperRow, 1);
+ this->insertCustomRow(whisperRow, WHISPER_ROW);
// Highlight settings for subscription messages
std::vector subRow = this->createRow();
@@ -107,12 +109,37 @@ void HighlightModel::afterInit()
subRow[Column::CaseSensitive]->setFlags(0);
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
- setFilePathItem(subRow[Column::SoundPath], subSound);
+ setFilePathItem(subRow[Column::SoundPath], subSound, false);
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
- setColorItem(subRow[Column::Color], *subColor);
+ setColorItem(subRow[Column::Color], *subColor, false);
this->insertCustomRow(subRow, 2);
+
+ // Highlight settings for redeemed highlight messages
+ std::vector redeemedRow = this->createRow();
+ setBoolItem(redeemedRow[Column::Pattern],
+ getSettings()->enableRedeemedHighlight.getValue(), true, false);
+ redeemedRow[Column::Pattern]->setData("Highlights redeemed with Bits",
+ Qt::DisplayRole);
+ setBoolItem(redeemedRow[Column::FlashTaskbar],
+ getSettings()->enableRedeemedHighlightTaskbar.getValue(), true,
+ false);
+ setBoolItem(redeemedRow[Column::PlaySound],
+ getSettings()->enableRedeemedHighlightSound.getValue(), true,
+ false);
+ redeemedRow[Column::UseRegex]->setFlags(0);
+ redeemedRow[Column::CaseSensitive]->setFlags(0);
+
+ QUrl RedeemedSound =
+ QUrl(getSettings()->redeemedHighlightSoundUrl.getValue());
+ setFilePathItem(redeemedRow[Column::SoundPath], RedeemedSound, false);
+
+ auto RedeemedColor =
+ ColorProvider::instance().color(ColorType::RedeemedHighlight);
+ setColorItem(redeemedRow[Column::Color], *RedeemedColor, false);
+
+ this->insertCustomRow(redeemedRow, 3);
}
void HighlightModel::customRowSetData(const std::vector &row,
@@ -128,7 +155,7 @@ void HighlightModel::customRowSetData(const std::vector &row,
{
getSettings()->enableSelfHighlight.setValue(value.toBool());
}
- else if (rowIndex == 1)
+ else if (rowIndex == WHISPER_ROW)
{
getSettings()->enableWhisperHighlight.setValue(
value.toBool());
@@ -137,6 +164,11 @@ void HighlightModel::customRowSetData(const std::vector &row,
{
getSettings()->enableSubHighlight.setValue(value.toBool());
}
+ else if (rowIndex == 3)
+ {
+ getSettings()->enableRedeemedHighlight.setValue(
+ value.toBool());
+ }
}
}
break;
@@ -148,7 +180,7 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->enableSelfHighlightTaskbar.setValue(
value.toBool());
}
- else if (rowIndex == 1)
+ else if (rowIndex == WHISPER_ROW)
{
getSettings()->enableWhisperHighlightTaskbar.setValue(
value.toBool());
@@ -158,6 +190,11 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->enableSubHighlightTaskbar.setValue(
value.toBool());
}
+ else if (rowIndex == 3)
+ {
+ getSettings()->enableRedeemedHighlightTaskbar.setValue(
+ value.toBool());
+ }
}
}
break;
@@ -169,7 +206,7 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->enableSelfHighlightSound.setValue(
value.toBool());
}
- else if (rowIndex == 1)
+ else if (rowIndex == WHISPER_ROW)
{
getSettings()->enableWhisperHighlightSound.setValue(
value.toBool());
@@ -179,6 +216,11 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->enableSubHighlightSound.setValue(
value.toBool());
}
+ else if (rowIndex == 3)
+ {
+ getSettings()->enableRedeemedHighlightSound.setValue(
+ value.toBool());
+ }
}
}
break;
@@ -199,7 +241,7 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->selfHighlightSoundUrl.setValue(
value.toString());
}
- else if (rowIndex == 1)
+ else if (rowIndex == WHISPER_ROW)
{
getSettings()->whisperHighlightSoundUrl.setValue(
value.toString());
@@ -209,6 +251,11 @@ void HighlightModel::customRowSetData(const std::vector &row,
getSettings()->subHighlightSoundUrl.setValue(
value.toString());
}
+ else if (rowIndex == 3)
+ {
+ getSettings()->redeemedHighlightSoundUrl.setValue(
+ value.toString());
+ }
}
}
break;
@@ -221,18 +268,27 @@ void HighlightModel::customRowSetData(const std::vector &row,
{
getSettings()->selfHighlightColor.setValue(colorName);
}
- else if (rowIndex == 1)
- {
- getSettings()->whisperHighlightColor.setValue(colorName);
- }
+ // else if (rowIndex == WHISPER_ROW)
+ // {
+ // getSettings()->whisperHighlightColor.setValue(colorName);
+ // }
else if (rowIndex == 2)
{
getSettings()->subHighlightColor.setValue(colorName);
}
+ else if (rowIndex == 3)
+ {
+ getSettings()->redeemedHighlightColor.setValue(colorName);
+ const_cast(ColorProvider::instance())
+ .updateColor(ColorType::RedeemedHighlight,
+ QColor(colorName));
+ }
}
}
break;
}
+
+ getApp()->windows->forceLayoutChannelViews();
}
} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightModel.hpp b/src/controllers/highlights/HighlightModel.hpp
index 13b85bca..92acd2e0 100644
--- a/src/controllers/highlights/HighlightModel.hpp
+++ b/src/controllers/highlights/HighlightModel.hpp
@@ -7,13 +7,11 @@
namespace chatterino {
-class HighlightController;
-
class HighlightModel : public SignalVectorModel
{
+public:
explicit HighlightModel(QObject *parent);
-public:
// Used here, in HighlightingPage and in UserHighlightModel
enum Column {
Pattern = 0,
@@ -25,6 +23,8 @@ public:
Color = 6
};
+ constexpr static int WHISPER_ROW = 1;
+
protected:
// turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(
@@ -40,8 +40,6 @@ protected:
virtual void customRowSetData(const std::vector &row,
int column, const QVariant &value, int role,
int rowIndex) override;
-
- friend class HighlightController;
};
} // namespace chatterino
diff --git a/src/controllers/highlights/HighlightPhrase.cpp b/src/controllers/highlights/HighlightPhrase.cpp
index 40942df3..5ffd5b36 100644
--- a/src/controllers/highlights/HighlightPhrase.cpp
+++ b/src/controllers/highlights/HighlightPhrase.cpp
@@ -2,6 +2,11 @@
namespace chatterino {
+QColor HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127);
+QColor HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR =
+ QColor(28, 126, 141, 90);
+QColor HighlightPhrase::FALLBACK_SUB_COLOR = QColor(196, 102, 255, 100);
+
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
{
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
diff --git a/src/controllers/highlights/HighlightPhrase.hpp b/src/controllers/highlights/HighlightPhrase.hpp
index 72849bb6..268ae354 100644
--- a/src/controllers/highlights/HighlightPhrase.hpp
+++ b/src/controllers/highlights/HighlightPhrase.hpp
@@ -70,6 +70,14 @@ public:
const QUrl &getSoundUrl() const;
const std::shared_ptr getColor() const;
+ /*
+ * XXX: Use the constexpr constructor here once we are building with
+ * Qt>=5.13.
+ */
+ static QColor FALLBACK_HIGHLIGHT_COLOR;
+ static QColor FALLBACK_REDEEMED_HIGHLIGHT_COLOR;
+ static QColor FALLBACK_SUB_COLOR;
+
private:
QString pattern_;
bool hasAlert_;
@@ -132,6 +140,8 @@ struct Deserialize {
chatterino::rj::getSafe(value, "color", encodedColor);
auto _color = QColor(encodedColor);
+ if (!_color.isValid())
+ _color = chatterino::HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR;
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
_isRegex, _isCaseSensitive,
diff --git a/src/controllers/highlights/UserHighlightModel.hpp b/src/controllers/highlights/UserHighlightModel.hpp
index dcc42a95..de433243 100644
--- a/src/controllers/highlights/UserHighlightModel.hpp
+++ b/src/controllers/highlights/UserHighlightModel.hpp
@@ -11,6 +11,7 @@ class HighlightController;
class UserHighlightModel : public SignalVectorModel
{
+public:
explicit UserHighlightModel(QObject *parent);
protected:
@@ -21,8 +22,6 @@ protected:
virtual void getRowFromItem(const HighlightPhrase &item,
std::vector &row) override;
-
- friend class HighlightController;
};
} // namespace chatterino
diff --git a/src/controllers/ignores/IgnoreController.cpp b/src/controllers/ignores/IgnoreController.cpp
deleted file mode 100644
index 5548ee5d..00000000
--- a/src/controllers/ignores/IgnoreController.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "controllers/ignores/IgnoreController.hpp"
-
-#include "Application.hpp"
-#include "controllers/ignores/IgnoreModel.hpp"
-
-#include
-
-namespace chatterino {
-
-void IgnoreController::initialize(Settings &, Paths &)
-{
- assert(!this->initialized_);
- this->initialized_ = true;
-
- for (const IgnorePhrase &phrase : this->ignoresSetting_.getValue())
- {
- this->phrases.appendItem(phrase);
- }
-
- this->phrases.delayedItemsChanged.connect([this] { //
- this->ignoresSetting_.setValue(this->phrases.getVector());
- });
-}
-
-IgnoreModel *IgnoreController::createModel(QObject *parent)
-{
- IgnoreModel *model = new IgnoreModel(parent);
- model->init(&this->phrases);
-
- return model;
-}
-
-} // namespace chatterino
diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp
index 1279f4a8..fed12f12 100644
--- a/src/controllers/ignores/IgnoreController.hpp
+++ b/src/controllers/ignores/IgnoreController.hpp
@@ -1,33 +1,7 @@
#pragma once
-#include "common/ChatterinoSetting.hpp"
-#include "common/SignalVector.hpp"
-#include "common/Singleton.hpp"
-#include "controllers/ignores/IgnorePhrase.hpp"
-
namespace chatterino {
-class Settings;
-class Paths;
-
-class IgnoreModel;
-
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
-class IgnoreController final : public Singleton
-{
-public:
- virtual void initialize(Settings &settings, Paths &paths) override;
-
- UnsortedSignalVector phrases;
-
- IgnoreModel *createModel(QObject *parent);
-
-private:
- bool initialized_ = false;
-
- ChatterinoSetting> ignoresSetting_ = {
- "/ignore/phrases"};
-};
-
} // namespace chatterino
diff --git a/src/controllers/ignores/IgnoreModel.hpp b/src/controllers/ignores/IgnoreModel.hpp
index 1b9a5099..473b61b9 100644
--- a/src/controllers/ignores/IgnoreModel.hpp
+++ b/src/controllers/ignores/IgnoreModel.hpp
@@ -7,10 +7,9 @@
namespace chatterino {
-class IgnoreController;
-
class IgnoreModel : public SignalVectorModel
{
+public:
explicit IgnoreModel(QObject *parent);
protected:
@@ -21,8 +20,6 @@ protected:
// turns a row in the model into a vector item
virtual void getRowFromItem(const IgnorePhrase &item,
std::vector &row) override;
-
- friend class IgnoreController;
};
} // namespace chatterino
diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp
index dce01904..d94cb234 100644
--- a/src/controllers/moderationactions/ModerationAction.cpp
+++ b/src/controllers/moderationactions/ModerationAction.cpp
@@ -71,11 +71,11 @@ ModerationAction::ModerationAction(const QString &action)
}
else if (action.startsWith("/ban "))
{
- this->image_ = Image::fromPixmap(getResources().buttons.ban);
+ this->imageToLoad_ = 1;
}
else if (action.startsWith("/delete "))
{
- this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
+ this->imageToLoad_ = 2;
}
else
{
@@ -100,6 +100,16 @@ bool ModerationAction::isImage() const
const boost::optional &ModerationAction::getImage() const
{
+ assertInGuiThread();
+
+ if (this->imageToLoad_ != 0)
+ {
+ if (this->imageToLoad_ == 1)
+ this->image_ = Image::fromPixmap(getResources().buttons.ban);
+ else if (this->imageToLoad_ == 2)
+ this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
+ }
+
return this->image_;
}
diff --git a/src/controllers/moderationactions/ModerationAction.hpp b/src/controllers/moderationactions/ModerationAction.hpp
index eed7579a..3b4532d9 100644
--- a/src/controllers/moderationactions/ModerationAction.hpp
+++ b/src/controllers/moderationactions/ModerationAction.hpp
@@ -25,10 +25,11 @@ public:
const QString &getAction() const;
private:
- boost::optional image_;
+ mutable boost::optional image_;
QString line1_;
QString line2_;
QString action_;
+ int imageToLoad_{};
};
} // namespace chatterino
diff --git a/src/controllers/moderationactions/ModerationActionModel.hpp b/src/controllers/moderationactions/ModerationActionModel.hpp
index e9f37236..e13b3b27 100644
--- a/src/controllers/moderationactions/ModerationActionModel.hpp
+++ b/src/controllers/moderationactions/ModerationActionModel.hpp
@@ -7,8 +7,6 @@
namespace chatterino {
-class ModerationActions;
-
class ModerationActionModel : public SignalVectorModel
{
public:
@@ -25,8 +23,6 @@ protected:
std::vector &row) override;
friend class HighlightController;
-
- friend class ModerationActions;
};
} // namespace chatterino
diff --git a/src/controllers/moderationactions/ModerationActions.cpp b/src/controllers/moderationactions/ModerationActions.cpp
deleted file mode 100644
index 0104c7de..00000000
--- a/src/controllers/moderationactions/ModerationActions.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-#include "ModerationActions.hpp"
-
-#include "Application.hpp"
-#include "controllers/moderationactions/ModerationActionModel.hpp"
-#include "singletons/Settings.hpp"
-
-#include
-
-namespace chatterino {
-
-ModerationActions::ModerationActions()
-{
-}
-
-void ModerationActions::initialize(Settings &settings, Paths &paths)
-{
- assert(!this->initialized_);
- this->initialized_ = true;
-
- this->setting_ =
- std::make_unique>>(
- "/moderation/actions");
-
- for (auto &val : this->setting_->getValue())
- {
- this->items.insertItem(val);
- }
-
- this->items.delayedItemsChanged.connect([this] { //
- this->setting_->setValue(this->items.getVector());
- });
-}
-
-ModerationActionModel *ModerationActions::createModel(QObject *parent)
-{
- ModerationActionModel *model = new ModerationActionModel(parent);
- model->init(&this->items);
-
- return model;
-}
-
-} // namespace chatterino
diff --git a/src/controllers/moderationactions/ModerationActions.hpp b/src/controllers/moderationactions/ModerationActions.hpp
deleted file mode 100644
index e1bf1b80..00000000
--- a/src/controllers/moderationactions/ModerationActions.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#pragma once
-
-#include "common/Singleton.hpp"
-
-#include "common/ChatterinoSetting.hpp"
-#include "common/SignalVector.hpp"
-#include "controllers/moderationactions/ModerationAction.hpp"
-
-namespace chatterino {
-
-class Settings;
-class Paths;
-
-class ModerationActionModel;
-
-class ModerationActions final : public Singleton
-{
-public:
- ModerationActions();
-
- virtual void initialize(Settings &settings, Paths &paths) override;
-
- UnsortedSignalVector items;
-
- ModerationActionModel *createModel(QObject *parent);
-
-private:
- std::unique_ptr>> setting_;
- bool initialized_ = false;
-};
-
-} // namespace chatterino
diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp
index b9777c80..2578558d 100644
--- a/src/controllers/notifications/NotificationController.cpp
+++ b/src/controllers/notifications/NotificationController.cpp
@@ -4,8 +4,8 @@
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "controllers/notifications/NotificationModel.hpp"
-#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
+#include "providers/twitch/api/Helix.hpp"
#include "singletons/Toasts.hpp"
#include "singletons/WindowManager.hpp"
#include "widgets/Window.hpp"
@@ -26,12 +26,11 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
this->initialized_ = true;
for (const QString &channelName : this->twitchSetting_.getValue())
{
- this->channelMap[Platform::Twitch].appendItem(channelName);
+ this->channelMap[Platform::Twitch].append(channelName);
}
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { //
- this->twitchSetting_.setValue(
- this->channelMap[Platform::Twitch].getVector());
+ this->twitchSetting_.setValue(this->channelMap[Platform::Twitch].raw());
});
/*
for (const QString &channelName : this->mixerSetting_.getValue()) {
@@ -81,18 +80,18 @@ bool NotificationController::isChannelNotified(const QString &channelName,
void NotificationController::addChannelNotification(const QString &channelName,
Platform p)
{
- channelMap[p].appendItem(channelName);
+ channelMap[p].append(channelName);
}
void NotificationController::removeChannelNotification(
const QString &channelName, Platform p)
{
- for (std::vector::size_type i = 0;
- i != channelMap[p].getVector().size(); i++)
+ for (std::vector::size_type i = 0; i != channelMap[p].raw().size();
+ i++)
{
- if (channelMap[p].getVector()[i].toLower() == channelName.toLower())
+ if (channelMap[p].raw()[i].toLower() == channelName.toLower())
{
- channelMap[p].removeItem(i);
+ channelMap[p].removeAt(i);
i--;
}
}
@@ -121,21 +120,21 @@ NotificationModel *NotificationController::createModel(QObject *parent,
Platform p)
{
NotificationModel *model = new NotificationModel(parent);
- model->init(&this->channelMap[p]);
+ model->initialize(&this->channelMap[p]);
return model;
}
void NotificationController::fetchFakeChannels()
{
for (std::vector::size_type i = 0;
- i != channelMap[Platform::Twitch].getVector().size(); i++)
+ i != channelMap[Platform::Twitch].raw().size(); i++)
{
auto chan = getApp()->twitch.server->getChannelOrEmpty(
- channelMap[Platform::Twitch].getVector()[i]);
+ channelMap[Platform::Twitch].raw()[i]);
if (chan->isEmpty())
{
getFakeTwitchChannelLiveStatus(
- channelMap[Platform::Twitch].getVector()[i]);
+ channelMap[Platform::Twitch].raw()[i]);
}
}
}
@@ -143,68 +142,52 @@ void NotificationController::fetchFakeChannels()
void NotificationController::getFakeTwitchChannelLiveStatus(
const QString &channelName)
{
- TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
- if (roomID.isEmpty())
- {
+ getHelix()->getStreamByName(
+ channelName,
+ [channelName, this](bool live, const auto &stream) {
+ qDebug() << "[TwitchChannel" << channelName
+ << "] Refreshing live status";
+
+ if (!live)
+ {
+ // Stream is offline
+ this->removeFakeChannel(channelName);
+ return;
+ }
+
+ // Stream is online
+ auto i = std::find(fakeTwitchChannels.begin(),
+ fakeTwitchChannels.end(), channelName);
+
+ if (i != fakeTwitchChannels.end())
+ {
+ // We have already pushed the live state of this stream
+ // Could not find stream in fake twitch channels!
+ return;
+ }
+
+ if (Toasts::isEnabled())
+ {
+ getApp()->toasts->sendChannelNotification(channelName,
+ Platform::Twitch);
+ }
+ if (getSettings()->notificationPlaySound)
+ {
+ getApp()->notifications->playSound();
+ }
+ if (getSettings()->notificationFlashTaskbar)
+ {
+ getApp()->windows->sendAlert();
+ }
+
+ // Indicate that we have pushed notifications for this stream
+ fakeTwitchChannels.push_back(channelName);
+ },
+ [channelName, this] {
qDebug() << "[TwitchChannel" << channelName
<< "] Refreshing live status (Missing ID)";
- removeFakeChannel(channelName);
- return;
- }
- qDebug() << "[TwitchChannel" << channelName
- << "] Refreshing live status";
-
- QString url("https://api.twitch.tv/kraken/streams/" + roomID);
- NetworkRequest::twitchRequest(url)
- .onSuccess([this, channelName](auto result) -> Outcome {
- rapidjson::Document document = result.parseRapidJson();
- if (!document.IsObject())
- {
- qDebug() << "[TwitchChannel:refreshLiveStatus] root is not "
- "an object";
- return Failure;
- }
-
- if (!document.HasMember("stream"))
- {
- qDebug() << "[TwitchChannel:refreshLiveStatus] Missing "
- "stream in root";
- return Failure;
- }
-
- const auto &stream = document["stream"];
-
- if (!stream.IsObject())
- {
- // Stream is offline (stream is most likely null)
- // removeFakeChannel(channelName);
- return Failure;
- }
- // Stream is live
- auto i = std::find(fakeTwitchChannels.begin(),
- fakeTwitchChannels.end(), channelName);
-
- if (!(i != fakeTwitchChannels.end()))
- {
- fakeTwitchChannels.push_back(channelName);
- if (Toasts::isEnabled())
- {
- getApp()->toasts->sendChannelNotification(
- channelName, Platform::Twitch);
- }
- if (getSettings()->notificationPlaySound)
- {
- getApp()->notifications->playSound();
- }
- if (getSettings()->notificationFlashTaskbar)
- {
- getApp()->windows->sendAlert();
- }
- }
- return Success;
- })
- .execute();
- });
+ this->removeFakeChannel(channelName);
+ });
}
void NotificationController::removeFakeChannel(const QString channelName)
diff --git a/src/controllers/notifications/NotificationController.hpp b/src/controllers/notifications/NotificationController.hpp
index 228348ad..e8d0c4ef 100644
--- a/src/controllers/notifications/NotificationController.hpp
+++ b/src/controllers/notifications/NotificationController.hpp
@@ -30,9 +30,9 @@ public:
void playSound();
- UnsortedSignalVector getVector(Platform p);
+ SignalVector getVector(Platform p);
- std::map> channelMap;
+ std::map> channelMap;
NotificationModel *createModel(QObject *parent, Platform p);
@@ -43,6 +43,7 @@ private:
void removeFakeChannel(const QString channelName);
void getFakeTwitchChannelLiveStatus(const QString &channelName);
+ // fakeTwitchChannels is a list of streams who are live that we have already sent out a notification for
std::vector fakeTwitchChannels;
QTimer *liveStatusTimer_;
diff --git a/src/controllers/pings/MutedChannelModel.cpp b/src/controllers/pings/MutedChannelModel.cpp
new file mode 100644
index 00000000..fc147350
--- /dev/null
+++ b/src/controllers/pings/MutedChannelModel.cpp
@@ -0,0 +1,28 @@
+#include "MutedChannelModel.hpp"
+
+#include "Application.hpp"
+#include "singletons/Settings.hpp"
+#include "util/StandardItemHelper.hpp"
+
+namespace chatterino {
+
+MutedChannelModel::MutedChannelModel(QObject *parent)
+ : SignalVectorModel(1, parent)
+{
+}
+
+// turn a vector item into a model row
+QString MutedChannelModel::getItemFromRow(std::vector &row,
+ const QString &original)
+{
+ return QString(row[0]->data(Qt::DisplayRole).toString());
+}
+
+// turn a model
+void MutedChannelModel::getRowFromItem(const QString &item,
+ std::vector &row)
+{
+ setStringItem(row[0], item);
+}
+
+} // namespace chatterino
diff --git a/src/controllers/pings/PingModel.hpp b/src/controllers/pings/MutedChannelModel.hpp
similarity index 79%
rename from src/controllers/pings/PingModel.hpp
rename to src/controllers/pings/MutedChannelModel.hpp
index 137be5e0..c53b3177 100644
--- a/src/controllers/pings/PingModel.hpp
+++ b/src/controllers/pings/MutedChannelModel.hpp
@@ -7,11 +7,11 @@
namespace chatterino {
-class PingController;
+class MutedChannelController;
-class PingModel : public SignalVectorModel
+class MutedChannelModel : public SignalVectorModel
{
- explicit PingModel(QObject *parent);
+ explicit MutedChannelModel(QObject *parent);
protected:
// turn a vector item into a model row
@@ -21,8 +21,6 @@ protected:
// turns a row in the model into a vector item
virtual void getRowFromItem(const QString &item,
std::vector &row) override;
-
- friend class PingController;
};
} // namespace chatterino
diff --git a/src/controllers/pings/PingController.cpp b/src/controllers/pings/PingController.cpp
deleted file mode 100644
index b61d6e79..00000000
--- a/src/controllers/pings/PingController.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "controllers/pings/PingController.hpp"
-#include "controllers/pings/PingModel.hpp"
-
-namespace chatterino {
-
-void PingController::initialize(Settings &settings, Paths &paths)
-{
- this->initialized_ = true;
- for (const QString &channelName : this->pingSetting_.getValue())
- {
- this->channelVector.appendItem(channelName);
- }
-
- this->channelVector.delayedItemsChanged.connect([this] { //
- this->pingSetting_.setValue(this->channelVector.getVector());
- });
-}
-
-PingModel *PingController::createModel(QObject *parent)
-{
- PingModel *model = new PingModel(parent);
- model->init(&this->channelVector);
- return model;
-}
-
-bool PingController::isMuted(const QString &channelName)
-{
- for (const auto &channel : this->channelVector)
- {
- if (channelName.toLower() == channel.toLower())
- {
- return true;
- }
- }
- return false;
-}
-
-void PingController::muteChannel(const QString &channelName)
-{
- channelVector.appendItem(channelName);
-}
-
-void PingController::unmuteChannel(const QString &channelName)
-{
- for (std::vector::size_type i = 0;
- i != channelVector.getVector().size(); i++)
- {
- if (channelVector.getVector()[i].toLower() == channelName.toLower())
- {
- channelVector.removeItem(i);
- i--;
- }
- }
-}
-
-bool PingController::toggleMuteChannel(const QString &channelName)
-{
- if (this->isMuted(channelName))
- {
- unmuteChannel(channelName);
- return false;
- }
- else
- {
- muteChannel(channelName);
- return true;
- }
-}
-
-} // namespace chatterino
diff --git a/src/controllers/pings/PingController.hpp b/src/controllers/pings/PingController.hpp
deleted file mode 100644
index 80805e85..00000000
--- a/src/controllers/pings/PingController.hpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include
-
-#include "common/SignalVector.hpp"
-#include "common/Singleton.hpp"
-#include "singletons/Settings.hpp"
-
-namespace chatterino {
-
-class Settings;
-class Paths;
-
-class PingModel;
-
-class PingController final : public Singleton, private QObject
-{
-public:
- virtual void initialize(Settings &settings, Paths &paths) override;
-
- bool isMuted(const QString &channelName);
- void muteChannel(const QString &channelName);
- void unmuteChannel(const QString &channelName);
- bool toggleMuteChannel(const QString &channelName);
-
- PingModel *createModel(QObject *parent);
-
-private:
- bool initialized_ = false;
-
- UnsortedSignalVector channelVector;
-
- ChatterinoSetting> pingSetting_ = {"/pings/muted"};
-};
-
-} // namespace chatterino
diff --git a/src/controllers/pings/PingModel.cpp b/src/controllers/pings/PingModel.cpp
deleted file mode 100644
index 28098a20..00000000
--- a/src/controllers/pings/PingModel.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "PingModel.hpp"
-
-#include "Application.hpp"
-#include "singletons/Settings.hpp"
-#include "util/StandardItemHelper.hpp"
-
-namespace chatterino {
-
-PingModel::PingModel(QObject *parent)
- : SignalVectorModel(1, parent)
-{
-}
-
-// turn a vector item into a model row
-QString PingModel::getItemFromRow(std::vector &row,
- const QString &original)
-{
- return QString(row[0]->data(Qt::DisplayRole).toString());
-}
-
-// turn a model
-void PingModel::getRowFromItem(const QString &item,
- std::vector &row)
-{
- setStringItem(row[0], item);
-}
-
-} // namespace chatterino
diff --git a/src/controllers/taggedusers/TaggedUsersController.cpp b/src/controllers/taggedusers/TaggedUsersController.cpp
deleted file mode 100644
index 2aa82a6c..00000000
--- a/src/controllers/taggedusers/TaggedUsersController.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "TaggedUsersController.hpp"
-
-#include "controllers/taggedusers/TaggedUsersModel.hpp"
-
-namespace chatterino {
-
-TaggedUsersController::TaggedUsersController()
-{
-}
-
-TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
-{
- TaggedUsersModel *model = new TaggedUsersModel(parent);
- model->init(&this->users);
-
- return model;
-}
-
-} // namespace chatterino
diff --git a/src/controllers/taggedusers/TaggedUsersController.hpp b/src/controllers/taggedusers/TaggedUsersController.hpp
deleted file mode 100644
index db9ff2d1..00000000
--- a/src/controllers/taggedusers/TaggedUsersController.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-#include "common/Singleton.hpp"
-
-#include "common/SignalVector.hpp"
-#include "controllers/taggedusers/TaggedUser.hpp"
-
-namespace chatterino {
-
-class TaggedUsersModel;
-
-class TaggedUsersController final : public Singleton
-{
-public:
- TaggedUsersController();
-
- SortedSignalVector> users;
-
- TaggedUsersModel *createModel(QObject *parent = nullptr);
-};
-
-} // namespace chatterino
diff --git a/src/main.cpp b/src/main.cpp
index 46346589..dfb42904 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -9,6 +9,8 @@
#include "common/Args.hpp"
#include "common/Modes.hpp"
#include "common/Version.hpp"
+#include "providers/twitch/api/Helix.hpp"
+#include "providers/twitch/api/Kraken.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
#include "util/IncognitoBrowser.hpp"
@@ -36,6 +38,9 @@ int main(int argc, char **argv)
}
else
{
+ Helix::initialize();
+ Kraken::initialize();
+
Paths *paths{};
try
diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp
index b4222301..4b115af3 100644
--- a/src/messages/Image.cpp
+++ b/src/messages/Image.cpp
@@ -41,6 +41,14 @@ namespace detail {
getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
+
+ auto totalLength = std::accumulate(
+ this->items_.begin(), this->items_.end(), 0UL,
+ [](auto init, auto &&frame) { return init + frame.duration; });
+
+ this->durationOffset_ = std::min(
+ int(getApp()->emotes->gifTimer.position() % totalLength), 60000);
+ this->processOffset();
}
Frames::~Frames()
@@ -58,17 +66,16 @@ namespace detail {
void Frames::advance()
{
- this->durationOffset_ += GIF_FRAME_LENGTH;
+ this->durationOffset_ += gifFrameLength;
+ this->processOffset();
+ }
+ void Frames::processOffset()
+ {
while (true)
{
this->index_ %= this->items_.size();
- // TODO: Figure out what this was supposed to achieve
- // if (this->index_ >= this->items_.size()) {
- // this->index_ = this->index_;
- // }
-
if (this->durationOffset_ > this->items_[this->index_].duration)
{
this->durationOffset_ -= this->items_[this->index_].duration;
@@ -219,10 +226,6 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
{
cache[url] = shared = ImagePtr(new Image(url, scale));
}
- else
- {
- // qDebug() << "same image created multiple times:" << url.string;
- }
return shared;
}
diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp
index f114d1f0..193ba578 100644
--- a/src/messages/Image.hpp
+++ b/src/messages/Image.hpp
@@ -35,6 +35,7 @@ namespace detail {
boost::optional first() const;
private:
+ void processOffset();
QVector> items_;
int index_{0};
int durationOffset_{0};
diff --git a/src/messages/LimitedQueue.hpp b/src/messages/LimitedQueue.hpp
index 61e8db56..22d1385e 100644
--- a/src/messages/LimitedQueue.hpp
+++ b/src/messages/LimitedQueue.hpp
@@ -125,8 +125,6 @@ public:
newChunks->at(0) = newFirstChunk;
this->chunks_ = newChunks;
- // qDebug() << acceptedItems.size();
- // qDebug() << this->chunks->at(0)->size();
if (this->chunks_->size() == 1)
{
diff --git a/src/messages/Message.cpp b/src/messages/Message.cpp
index 218fa3a7..912eeefe 100644
--- a/src/messages/Message.cpp
+++ b/src/messages/Message.cpp
@@ -35,6 +35,12 @@ SBHighlight Message::getScrollBarHighlight() const
return SBHighlight(
ColorProvider::instance().color(ColorType::Subscription));
}
+ else if (this->flags.has(MessageFlag::RedeemedHighlight))
+ {
+ return SBHighlight(
+ ColorProvider::instance().color(ColorType::RedeemedHighlight),
+ SBHighlight::Default, true);
+ }
return SBHighlight();
}
diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp
index 3b5bb97a..e9456dc8 100644
--- a/src/messages/Message.hpp
+++ b/src/messages/Message.hpp
@@ -34,6 +34,7 @@ enum class MessageFlag : uint32_t {
HighlightedWhisper = (1 << 17),
Debug = (1 << 18),
Similar = (1 << 19),
+ RedeemedHighlight = (1 << 20),
};
using MessageFlags = FlagsEnum;
diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp
index 6e4b8115..c215a60a 100644
--- a/src/messages/MessageBuilder.cpp
+++ b/src/messages/MessageBuilder.cpp
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "common/LinkParser.hpp"
+#include "controllers/accounts/AccountController.hpp"
#include "messages/Image.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
@@ -170,9 +171,12 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
this->message().searchText = text;
}
+// XXX: This does not belong in the MessageBuilder, this should be part of the TwitchMessageBuilder
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
: MessageBuilder()
{
+ auto current = getApp()->accounts->twitch.getCurrent();
+
this->emplace();
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::Timeout);
@@ -181,43 +185,74 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
QString text;
- if (action.isBan())
+ if (action.target.id == current->getUserId())
{
- if (action.reason.isEmpty())
+ text.append("You were ");
+ if (action.isBan())
{
- text = QString("%1 banned %2.") //
- .arg(action.source.name)
- .arg(action.target.name);
+ text.append("banned");
}
else
{
- text = QString("%1 banned %2: \"%3\".") //
- .arg(action.source.name)
- .arg(action.target.name)
- .arg(action.reason);
+ text.append(
+ QString("timed out for %1").arg(formatTime(action.duration)));
+ }
+
+ if (!action.source.name.isEmpty())
+ {
+ text.append(" by ");
+ text.append(action.source.name);
+ }
+
+ if (action.reason.isEmpty())
+ {
+ text.append(".");
+ }
+ else
+ {
+ text.append(QString(": \"%1\".").arg(action.reason));
}
}
else
{
- if (action.reason.isEmpty())
+ if (action.isBan())
{
- text = QString("%1 timed out %2 for %3.") //
- .arg(action.source.name)
- .arg(action.target.name)
- .arg(formatTime(action.duration));
+ if (action.reason.isEmpty())
+ {
+ text = QString("%1 banned %2.") //
+ .arg(action.source.name)
+ .arg(action.target.name);
+ }
+ else
+ {
+ text = QString("%1 banned %2: \"%3\".") //
+ .arg(action.source.name)
+ .arg(action.target.name)
+ .arg(action.reason);
+ }
}
else
{
- text = QString("%1 timed out %2 for %3: \"%4\".") //
- .arg(action.source.name)
- .arg(action.target.name)
- .arg(formatTime(action.duration))
- .arg(action.reason);
- }
+ if (action.reason.isEmpty())
+ {
+ text = QString("%1 timed out %2 for %3.") //
+ .arg(action.source.name)
+ .arg(action.target.name)
+ .arg(formatTime(action.duration));
+ }
+ else
+ {
+ text = QString("%1 timed out %2 for %3: \"%4\".") //
+ .arg(action.source.name)
+ .arg(action.target.name)
+ .arg(formatTime(action.duration))
+ .arg(action.reason);
+ }
- if (count > 1)
- {
- text.append(QString(" (%1 times)").arg(count));
+ if (count > 1)
+ {
+ text.append(QString(" (%1 times)").arg(count));
+ }
}
}
diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp
index 0defed59..d49ca4ad 100644
--- a/src/messages/MessageElement.cpp
+++ b/src/messages/MessageElement.cpp
@@ -1,7 +1,6 @@
#include "messages/MessageElement.hpp"
#include "Application.hpp"
-#include "controllers/moderationactions/ModerationActions.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
@@ -165,21 +164,6 @@ MessageLayoutElement *EmoteElement::makeImageLayoutElement(
return new ImageLayoutElement(*this, image, size);
}
-// MOD BADGE
-ModBadgeElement::ModBadgeElement(const EmotePtr &data,
- MessageElementFlags flags_)
- : EmoteElement(data, flags_)
-{
-}
-
-MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
- const ImagePtr &image, const QSize &size)
-{
- static const QColor modBadgeBackgroundColor("#34AE0A");
- return new ImageWithBackgroundLayoutElement(*this, image, size,
- modBadgeBackgroundColor);
-}
-
// BADGE
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
: MessageElement(flags)
@@ -201,8 +185,7 @@ void BadgeElement::addToContainer(MessageLayoutContainer &container,
auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height()));
- container.addElement((new ImageLayoutElement(*this, image, size))
- ->setLink(this->getLink()));
+ container.addElement(this->makeImageLayoutElement(image, size));
}
}
@@ -211,6 +194,34 @@ EmotePtr BadgeElement::getEmote() const
return this->emote_;
}
+MessageLayoutElement *BadgeElement::makeImageLayoutElement(
+ const ImagePtr &image, const QSize &size)
+{
+ auto element =
+ (new ImageLayoutElement(*this, image, size))->setLink(this->getLink());
+
+ return element;
+}
+
+// MOD BADGE
+ModBadgeElement::ModBadgeElement(const EmotePtr &data,
+ MessageElementFlags flags_)
+ : BadgeElement(data, flags_)
+{
+}
+
+MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
+ const ImagePtr &image, const QSize &size)
+{
+ static const QColor modBadgeBackgroundColor("#34AE0A");
+
+ auto element = (new ImageWithBackgroundLayoutElement(
+ *this, image, size, modBadgeBackgroundColor))
+ ->setLink(this->getLink());
+
+ return element;
+}
+
// TEXT
TextElement::TextElement(const QString &text, MessageElementFlags flags,
const MessageColor &color, FontStyle style)
@@ -374,8 +385,8 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
{
QSize size(int(container.getScale() * 16),
int(container.getScale() * 16));
-
- for (const auto &action : getApp()->moderationActions->items)
+ auto actions = getCSettings().moderationActions.readOnly();
+ for (const auto &action : *actions)
{
if (auto image = action.getImage())
{
diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp
index 7d1a00c6..110cf917 100644
--- a/src/messages/MessageElement.hpp
+++ b/src/messages/MessageElement.hpp
@@ -223,17 +223,6 @@ private:
EmotePtr emote_;
};
-// Behaves like an emote element, except it creates a different image layout element that draws the mod badge background
-class ModBadgeElement : public EmoteElement
-{
-public:
- ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
-
-protected:
- MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
- const QSize &size) override;
-};
-
class BadgeElement : public MessageElement
{
public:
@@ -244,10 +233,24 @@ public:
EmotePtr getEmote() const;
+protected:
+ virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
+ const QSize &size);
+
private:
EmotePtr emote_;
};
+class ModBadgeElement : public BadgeElement
+{
+public:
+ ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
+
+protected:
+ MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
+ const QSize &size) override;
+};
+
// contains a text, formated depending on the preferences
class TimestampElement : public MessageElement
{
diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp
index c048bd62..e7eaa2ef 100644
--- a/src/messages/layouts/MessageLayout.cpp
+++ b/src/messages/layouts/MessageLayout.cpp
@@ -263,6 +263,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
Selection & /*selection*/)
{
auto app = getApp();
+ auto settings = getSettings();
QPainter painter(buffer);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
@@ -296,6 +297,14 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
backgroundColor,
*ColorProvider::instance().color(ColorType::Subscription));
}
+ else if (this->message_->flags.has(MessageFlag::RedeemedHighlight) &&
+ settings->enableRedeemedHighlight.getValue())
+ {
+ // Blend highlight color with usual background color
+ backgroundColor = blendColors(
+ backgroundColor,
+ *ColorProvider::instance().color(ColorType::RedeemedHighlight));
+ }
else if (this->message_->flags.has(MessageFlag::AutoMod))
{
backgroundColor = QColor("#404040");
diff --git a/src/providers/colors/ColorProvider.cpp b/src/providers/colors/ColorProvider.cpp
index 492689aa..ad35db05 100644
--- a/src/providers/colors/ColorProvider.cpp
+++ b/src/providers/colors/ColorProvider.cpp
@@ -1,6 +1,5 @@
#include "providers/colors/ColorProvider.hpp"
-#include "controllers/highlights/HighlightController.hpp"
#include "singletons/Theme.hpp"
namespace chatterino {
@@ -38,12 +37,12 @@ QSet ColorProvider::recentColors() const
* Currently, only colors used in highlight phrases are considered. This
* may change at any point in the future.
*/
- for (auto phrase : getApp()->highlights->phrases)
+ for (auto phrase : getSettings()->highlightedMessages)
{
retVal.insert(*phrase.getColor());
}
- for (auto userHl : getApp()->highlights->highlightedUsers)
+ for (auto userHl : getSettings()->highlightedUsers)
{
retVal.insert(*userHl.getColor());
}
@@ -65,7 +64,6 @@ void ColorProvider::initTypeColorMap()
{
// Read settings for custom highlight colors and save them in map.
// If no custom values can be found, set up default values instead.
- auto backgrounds = getApp()->themes->messages.backgrounds;
QString customColor = getSettings()->selfHighlightColor;
if (QColor(customColor).isValid())
@@ -77,7 +75,8 @@ void ColorProvider::initTypeColorMap()
{
this->typeColorMap_.insert(
{ColorType::SelfHighlight,
- std::make_shared(backgrounds.highlighted)});
+ std::make_shared(
+ HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR)});
}
customColor = getSettings()->subHighlightColor;
@@ -90,7 +89,7 @@ void ColorProvider::initTypeColorMap()
{
this->typeColorMap_.insert(
{ColorType::Subscription,
- std::make_shared(backgrounds.subscription)});
+ std::make_shared(HighlightPhrase::FALLBACK_SUB_COLOR)});
}
customColor = getSettings()->whisperHighlightColor;
@@ -103,22 +102,41 @@ void ColorProvider::initTypeColorMap()
{
this->typeColorMap_.insert(
{ColorType::Whisper,
- std::make_shared(backgrounds.highlighted)});
+ std::make_shared(
+ HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR)});
+ }
+
+ customColor = getSettings()->redeemedHighlightColor;
+ if (QColor(customColor).isValid())
+ {
+ this->typeColorMap_.insert({ColorType::RedeemedHighlight,
+ std::make_shared(customColor)});
+ }
+ else
+ {
+ this->typeColorMap_.insert(
+ {ColorType::RedeemedHighlight,
+ std::make_shared(
+ HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR)});
}
}
void ColorProvider::initDefaultColors()
{
// Init default colors
- this->defaultColors_.emplace_back(31, 141, 43, 127); // Green-ish
- this->defaultColors_.emplace_back(28, 126, 141, 127); // Blue-ish
- this->defaultColors_.emplace_back(136, 141, 49, 127); // Golden-ish
- this->defaultColors_.emplace_back(143, 48, 24, 127); // Red-ish
- this->defaultColors_.emplace_back(28, 141, 117, 127); // Cyan-ish
+ this->defaultColors_.emplace_back(75, 127, 107, 100); // Teal
+ this->defaultColors_.emplace_back(105, 127, 63, 100); // Olive
+ this->defaultColors_.emplace_back(63, 83, 127, 100); // Blue
+ this->defaultColors_.emplace_back(72, 127, 63, 100); // Green
- auto backgrounds = getApp()->themes->messages.backgrounds;
- this->defaultColors_.push_back(backgrounds.highlighted);
- this->defaultColors_.push_back(backgrounds.subscription);
+ this->defaultColors_.emplace_back(31, 141, 43, 115); // Green
+ this->defaultColors_.emplace_back(28, 126, 141, 90); // Blue
+ this->defaultColors_.emplace_back(136, 141, 49, 90); // Golden
+ this->defaultColors_.emplace_back(143, 48, 24, 127); // Red
+ this->defaultColors_.emplace_back(28, 141, 117, 90); // Cyan
+
+ this->defaultColors_.push_back(HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR);
+ this->defaultColors_.push_back(HighlightPhrase::FALLBACK_SUB_COLOR);
}
} // namespace chatterino
diff --git a/src/providers/colors/ColorProvider.hpp b/src/providers/colors/ColorProvider.hpp
index e038b17f..1595c53d 100644
--- a/src/providers/colors/ColorProvider.hpp
+++ b/src/providers/colors/ColorProvider.hpp
@@ -2,7 +2,12 @@
namespace chatterino {
-enum class ColorType { SelfHighlight, Subscription, Whisper };
+enum class ColorType {
+ SelfHighlight,
+ Subscription,
+ Whisper,
+ RedeemedHighlight
+};
class ColorProvider
{
diff --git a/src/providers/ffz/FfzEmotes.cpp b/src/providers/ffz/FfzEmotes.cpp
index 29dbac52..8589e3a6 100644
--- a/src/providers/ffz/FfzEmotes.cpp
+++ b/src/providers/ffz/FfzEmotes.cpp
@@ -102,7 +102,7 @@ namespace {
modBadge = std::make_shared(Emote{
{""},
modBadgeImageSet,
- Tooltip{"Twitch Channel Moderator"},
+ Tooltip{"Moderator"},
modBadge1x,
});
}
diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp
index 0a3019e5..2618465a 100644
--- a/src/providers/irc/AbstractIrcServer.cpp
+++ b/src/providers/irc/AbstractIrcServer.cpp
@@ -71,8 +71,30 @@ AbstractIrcServer::AbstractIrcServer()
});
}
+void AbstractIrcServer::initializeIrc()
+{
+ assert(!this->initialized_);
+
+ if (this->hasSeparateWriteConnection())
+ {
+ this->initializeConnectionSignals(this->writeConnection_.get(),
+ ConnectionType::Write);
+ this->initializeConnectionSignals(this->readConnection_.get(),
+ ConnectionType::Read);
+ }
+ else
+ {
+ this->initializeConnectionSignals(this->readConnection_.get(),
+ ConnectionType::Both);
+ }
+
+ this->initialized_ = true;
+}
+
void AbstractIrcServer::connect()
{
+ assert(this->initialized_);
+
this->disconnect();
if (this->hasSeparateWriteConnection())
diff --git a/src/providers/irc/AbstractIrcServer.hpp b/src/providers/irc/AbstractIrcServer.hpp
index 20d78249..1c865ef5 100644
--- a/src/providers/irc/AbstractIrcServer.hpp
+++ b/src/providers/irc/AbstractIrcServer.hpp
@@ -21,6 +21,10 @@ public:
virtual ~AbstractIrcServer() = default;
+ // initializeIrc must be called from the derived class
+ // this allows us to initialize the abstract irc server based on the derived class's parameters
+ void initializeIrc();
+
// connection
void connect();
void disconnect();
@@ -45,8 +49,15 @@ public:
protected:
AbstractIrcServer();
+ // initializeConnectionSignals is called on a connection once in its lifetime.
+ // it can be used to connect signals to your class
+ virtual void initializeConnectionSignals(IrcConnection *connection,
+ ConnectionType type){};
+
+ // initializeConnection is called every time before we try to connect to the irc server
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) = 0;
+
virtual std::shared_ptr createChannel(
const QString &channelName) = 0;
@@ -83,6 +94,8 @@ private:
// bool autoReconnect_ = false;
pajlada::Signals::SignalHolder connections_;
+
+ bool initialized_{false};
};
} // namespace chatterino
diff --git a/src/providers/irc/Irc2.cpp b/src/providers/irc/Irc2.cpp
index 0a8f2ed6..2f685be6 100644
--- a/src/providers/irc/Irc2.cpp
+++ b/src/providers/irc/Irc2.cpp
@@ -143,7 +143,7 @@ Irc::Irc()
QAbstractTableModel *Irc::newConnectionModel(QObject *parent)
{
auto model = new Model(parent);
- model->init(&this->connections);
+ model->initialize(&this->connections);
return model;
}
@@ -252,7 +252,7 @@ void Irc::load()
{
ids.insert(data.id);
- this->connections.appendItem(data);
+ this->connections.append(data);
}
}
}
diff --git a/src/providers/irc/Irc2.hpp b/src/providers/irc/Irc2.hpp
index de484924..0cdc60c7 100644
--- a/src/providers/irc/Irc2.hpp
+++ b/src/providers/irc/Irc2.hpp
@@ -41,7 +41,7 @@ public:
static inline void *const noEraseCredentialCaller =
reinterpret_cast(1);
- UnsortedSignalVector connections;
+ SignalVector connections;
QAbstractTableModel *newConnectionModel(QObject *parent);
ChannelPtr getOrAddChannel(int serverId, QString name);
diff --git a/src/providers/irc/IrcServer.cpp b/src/providers/irc/IrcServer.cpp
index dd7505ff..1b3aea5f 100644
--- a/src/providers/irc/IrcServer.cpp
+++ b/src/providers/irc/IrcServer.cpp
@@ -15,6 +15,8 @@ namespace chatterino {
IrcServer::IrcServer(const IrcServerData &data)
: data_(new IrcServerData(data))
{
+ this->initializeIrc();
+
this->connect();
}
@@ -51,6 +53,57 @@ const QString &IrcServer::nick()
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
}
+void IrcServer::initializeConnectionSignals(IrcConnection *connection,
+ ConnectionType type)
+{
+ assert(type == Both);
+
+ QObject::connect(
+ connection, &Communi::IrcConnection::socketError, this,
+ [this](QAbstractSocket::SocketError error) {
+ static int index =
+ QAbstractSocket::staticMetaObject.indexOfEnumerator(
+ "SocketError");
+
+ std::lock_guard lock(this->channelMutex);
+
+ for (auto &&weak : this->channels)
+ {
+ if (auto shared = weak.lock())
+ {
+ shared->addMessage(makeSystemMessage(
+ QStringLiteral("Socket error: ") +
+ QAbstractSocket::staticMetaObject.enumerator(index)
+ .valueToKey(error)));
+ }
+ }
+ });
+
+ QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
+ this, [](const QString &reserved, QString *result) {
+ *result = reserved + (std::rand() % 100);
+ });
+
+ QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
+ this, [this](Communi::IrcNoticeMessage *message) {
+ MessageBuilder builder;
+
+ builder.emplace();
+ builder.emplace(
+ message->nick(), MessageElementFlag::Username);
+ builder.emplace(
+ "-> you:", MessageElementFlag::Username);
+ builder.emplace(message->content(),
+ MessageElementFlag::Text);
+
+ auto msg = builder.release();
+
+ for (auto &&weak : this->channels)
+ if (auto shared = weak.lock())
+ shared->addMessage(msg);
+ });
+}
+
void IrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
@@ -90,47 +143,6 @@ void IrcServer::initializeConnection(IrcConnection *connection,
this->open(Both);
}
}
-
- QObject::connect(
- connection, &Communi::IrcConnection::socketError, this,
- [this](QAbstractSocket::SocketError error) {
- static int index =
- QAbstractSocket::staticMetaObject.indexOfEnumerator(
- "SocketError");
-
- std::lock_guard lock(this->channelMutex);
-
- for (auto &&weak : this->channels)
- if (auto shared = weak.lock())
- shared->addMessage(makeSystemMessage(
- QStringLiteral("Socket error: ") +
- QAbstractSocket::staticMetaObject.enumerator(index)
- .valueToKey(error)));
- });
-
- QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
- this, [](const QString &reserved, QString *result) {
- *result = reserved + (std::rand() % 100);
- });
-
- QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
- this, [this](Communi::IrcNoticeMessage *message) {
- MessageBuilder builder;
-
- builder.emplace();
- builder.emplace(
- message->nick(), MessageElementFlag::Username);
- builder.emplace(
- "-> you:", MessageElementFlag::Username);
- builder.emplace(message->content(),
- MessageElementFlag::Text);
-
- auto msg = builder.release();
-
- for (auto &&weak : this->channels)
- if (auto shared = weak.lock())
- shared->addMessage(msg);
- });
}
std::shared_ptr IrcServer::createChannel(const QString &channelName)
diff --git a/src/providers/irc/IrcServer.hpp b/src/providers/irc/IrcServer.hpp
index 0b5a0aad..6c1251fa 100644
--- a/src/providers/irc/IrcServer.hpp
+++ b/src/providers/irc/IrcServer.hpp
@@ -21,6 +21,8 @@ public:
// AbstractIrcServer interface
protected:
+ void initializeConnectionSignals(IrcConnection *connection,
+ ConnectionType type) override;
void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
std::shared_ptr createChannel(const QString &channelName) override;
diff --git a/src/providers/twitch/ChatroomChannel.cpp b/src/providers/twitch/ChatroomChannel.cpp
deleted file mode 100644
index 5f393e42..00000000
--- a/src/providers/twitch/ChatroomChannel.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#include "ChatroomChannel.hpp"
-
-#include
-#include "TwitchApi.hpp"
-#include "common/Common.hpp"
-#include "messages/Emote.hpp"
-#include "providers/bttv/BttvEmotes.hpp"
-#include "providers/bttv/LoadBttvChannelEmote.hpp"
-#include "singletons/Emotes.hpp"
-
-namespace chatterino {
-
-ChatroomChannel::ChatroomChannel(const QString &channelName,
- TwitchBadges &globalTwitchBadges,
- BttvEmotes &globalBttv, FfzEmotes &globalFfz)
- : TwitchChannel(channelName, globalTwitchBadges, globalBttv, globalFfz)
-{
- auto listRef = channelName.splitRef(":");
- if (listRef.size() > 2)
- {
- this->chatroomOwnerId = listRef[1].toString();
- }
-}
-
-void ChatroomChannel::refreshBTTVChannelEmotes()
-{
- if (this->chatroomOwnerId.isEmpty())
- {
- return;
- }
- TwitchApi::findUserName(
- this->chatroomOwnerId,
- [this, weak = weakOf(this)](QString username) {
- BttvEmotes::loadChannel(username, [this, weak](auto &&emoteMap) {
- if (auto shared = weak.lock())
- this->bttvEmotes_.set(
- std::make_shared(std::move(emoteMap)));
- });
- if (auto shared = weak.lock())
- {
- this->chatroomOwnerName = username;
- }
- });
-}
-void ChatroomChannel::refreshFFZChannelEmotes()
-{
- if (this->chatroomOwnerId.isEmpty())
- {
- return;
- }
- FfzEmotes::loadChannel(
- this->chatroomOwnerId,
- [this](auto &&emoteMap) {
- this->ffzEmotes_.set(
- std::make_shared(std::move(emoteMap)));
- },
- [this](auto &&modBadge) {
- this->ffzCustomModBadge_.set(std::move(modBadge));
- });
-}
-
-const QString &ChatroomChannel::getDisplayName() const
-{
- return this->chatroomOwnerName;
-}
-
-} // namespace chatterino
diff --git a/src/providers/twitch/ChatroomChannel.hpp b/src/providers/twitch/ChatroomChannel.hpp
deleted file mode 100644
index 2ed24914..00000000
--- a/src/providers/twitch/ChatroomChannel.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-#include "TwitchChannel.hpp"
-
-#include
-#include
-
-namespace chatterino {
-
-class ChatroomChannel : public TwitchChannel
-{
-protected:
- explicit ChatroomChannel(const QString &channelName,
- TwitchBadges &globalTwitchBadges,
- BttvEmotes &globalBttv, FfzEmotes &globalFfz);
- virtual void refreshBTTVChannelEmotes() override;
- virtual void refreshFFZChannelEmotes() override;
- virtual const QString &getDisplayName() const override;
-
- QString chatroomOwnerId;
- QString chatroomOwnerName;
-
- friend class TwitchIrcServer;
- friend class TwitchMessageBuilder;
- friend class IrcMessageHandler;
-};
-
-} // namespace chatterino
diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp
index 7a456eca..833586d4 100644
--- a/src/providers/twitch/IrcMessageHandler.cpp
+++ b/src/providers/twitch/IrcMessageHandler.cpp
@@ -2,7 +2,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
-#include "controllers/highlights/HighlightController.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "providers/twitch/TwitchAccountManager.hpp"
@@ -241,7 +240,6 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
if (highlighted)
{
server.mentionsChannel->addMessage(msg);
- getApp()->highlights->addHighlight(msg);
}
}
@@ -453,7 +451,6 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
auto app = getApp();
- qDebug() << "Received whisper!";
MessageParseArgs args;
args.isReceivedWhisper = true;
diff --git a/src/providers/twitch/PartialTwitchUser.cpp b/src/providers/twitch/PartialTwitchUser.cpp
deleted file mode 100644
index 5407fbad..00000000
--- a/src/providers/twitch/PartialTwitchUser.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#include "providers/twitch/PartialTwitchUser.hpp"
-
-#include "common/Common.hpp"
-#include "common/NetworkRequest.hpp"
-#include "providers/twitch/TwitchCommon.hpp"
-
-#include
-#include
-
-namespace chatterino {
-
-PartialTwitchUser PartialTwitchUser::byName(const QString &username)
-{
- PartialTwitchUser user;
- user.username_ = username;
-
- return user;
-}
-
-PartialTwitchUser PartialTwitchUser::byId(const QString &id)
-{
- PartialTwitchUser user;
- user.id_ = id;
-
- return user;
-}
-
-void PartialTwitchUser::getId(std::function successCallback,
- const QObject *caller)
-{
- getId(
- successCallback, [] {}, caller);
-}
-void PartialTwitchUser::getId(std::function successCallback,
- std::function failureCallback,
- const QObject *caller)
-{
- assert(!this->username_.isEmpty());
-
- NetworkRequest("https://api.twitch.tv/kraken/users?login=" +
- this->username_)
- .caller(caller)
- .authorizeTwitchV5(getDefaultClientID())
- .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
- auto root = result.parseJson();
- if (!root.value("users").isArray())
- {
- qDebug()
- << "API Error while getting user id, users is not an array";
- failureCallback();
- return Failure;
- }
-
- auto users = root.value("users").toArray();
- if (users.size() != 1)
- {
- qDebug() << "API Error while getting user id, users array size "
- "is not 1";
- failureCallback();
- return Failure;
- }
- if (!users[0].isObject())
- {
- qDebug() << "API Error while getting user id, first user is "
- "not an object";
- failureCallback();
- return Failure;
- }
- auto firstUser = users[0].toObject();
- auto id = firstUser.value("_id");
- if (!id.isString())
- {
- qDebug() << "API Error: while getting user id, first user "
- "object `_id` key is not a string";
- failureCallback();
- return Failure;
- }
- successCallback(id.toString());
-
- return Success;
- })
- .execute();
-}
-
-} // namespace chatterino
diff --git a/src/providers/twitch/PartialTwitchUser.hpp b/src/providers/twitch/PartialTwitchUser.hpp
deleted file mode 100644
index 6537bb27..00000000
--- a/src/providers/twitch/PartialTwitchUser.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include
-#include
-
-#include
-
-namespace chatterino {
-
-// Experimental class to test a method of calling APIs on twitch users
-class PartialTwitchUser
-{
- PartialTwitchUser() = default;
-
- QString username_;
- QString id_;
-
-public:
- static PartialTwitchUser byName(const QString &username);
- static PartialTwitchUser byId(const QString &id);
-
- void getId(std::function successCallback,
- const QObject *caller = nullptr);
-
- void getId(std::function successCallback,
- std::function failureCallback,
- const QObject *caller = nullptr);
-};
-
-} // namespace chatterino
diff --git a/src/providers/twitch/PubsubClient.cpp b/src/providers/twitch/PubsubClient.cpp
index 9cf57c28..c48d02c5 100644
--- a/src/providers/twitch/PubsubClient.cpp
+++ b/src/providers/twitch/PubsubClient.cpp
@@ -820,14 +820,12 @@ void PubSub::listen(rapidjson::Document &&msg)
this->addClient();
- qDebug() << "Added to the back of the queue";
this->requests.emplace_back(
std::make_unique(std::move(msg)));
}
bool PubSub::tryListen(rapidjson::Document &msg)
{
- qDebug() << "tryListen with" << this->clients.size() << "clients";
for (const auto &p : this->clients)
{
const auto &client = p.second;
@@ -1064,7 +1062,6 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
else
{
qDebug() << "Invalid whisper type:" << whisperType.c_str();
- assert(false);
return;
}
}
diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp
index 13b4d409..9ece767a 100644
--- a/src/providers/twitch/TwitchAccount.cpp
+++ b/src/providers/twitch/TwitchAccount.cpp
@@ -6,8 +6,8 @@
#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
-#include "providers/twitch/PartialTwitchUser.hpp"
#include "providers/twitch/TwitchCommon.hpp"
+#include "providers/twitch/api/Helix.hpp"
#include "singletons/Emotes.hpp"
#include "util/RapidjsonHelpers.hpp"
@@ -151,12 +151,14 @@ void TwitchAccount::ignore(
const QString &targetName,
std::function onFinished)
{
- const auto onIdFetched = [this, targetName,
- onFinished](QString targetUserId) {
- this->ignoreByID(targetUserId, targetName, onFinished); //
+ const auto onUserFetched = [this, targetName,
+ onFinished](const auto &user) {
+ this->ignoreByID(user.id, targetName, onFinished); //
};
- PartialTwitchUser::byName(targetName).getId(onIdFetched);
+ const auto onUserFetchFailed = [] {};
+
+ getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
}
void TwitchAccount::ignoreByID(
@@ -226,12 +228,14 @@ void TwitchAccount::unignore(
const QString &targetName,
std::function onFinished)
{
- const auto onIdFetched = [this, targetName,
- onFinished](QString targetUserId) {
- this->unignoreByID(targetUserId, targetName, onFinished); //
+ const auto onUserFetched = [this, targetName,
+ onFinished](const auto &user) {
+ this->unignoreByID(user.id, targetName, onFinished); //
};
- PartialTwitchUser::byName(targetName).getId(onIdFetched);
+ const auto onUserFetchFailed = [] {};
+
+ getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
}
void TwitchAccount::unignoreByID(
@@ -270,28 +274,18 @@ void TwitchAccount::unignoreByID(
void TwitchAccount::checkFollow(const QString targetUserID,
std::function onFinished)
{
- QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
- "/follows/channels/" + targetUserID);
+ const auto onResponse = [onFinished](bool following, const auto &record) {
+ if (!following)
+ {
+ onFinished(FollowResult_NotFollowing);
+ return;
+ }
- NetworkRequest(url)
+ onFinished(FollowResult_Following);
+ };
- .authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
- .onError([=](NetworkResult result) {
- if (result.status() == 203)
- {
- onFinished(FollowResult_NotFollowing);
- }
- else
- {
- onFinished(FollowResult_Failed);
- }
- })
- .onSuccess([=](auto result) -> Outcome {
- auto document = result.parseRapidJson();
- onFinished(FollowResult_Following);
- return Success;
- })
- .execute();
+ getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse,
+ [] {});
}
void TwitchAccount::followUser(const QString userID,
@@ -392,7 +386,6 @@ void TwitchAccount::autoModAllow(const QString msgID)
QString url("https://api.twitch.tv/kraken/chat/twitchbot/approve");
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
- qDebug() << qba;
NetworkRequest(url, NetworkRequestType::Post)
.header("Content-Type", "application/json")
@@ -412,7 +405,6 @@ void TwitchAccount::autoModDeny(const QString msgID)
QString url("https://api.twitch.tv/kraken/chat/twitchbot/deny");
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
- qDebug() << qba;
NetworkRequest(url, NetworkRequestType::Post)
.header("Content-Type", "application/json")
diff --git a/src/providers/twitch/TwitchAccountManager.cpp b/src/providers/twitch/TwitchAccountManager.cpp
index b772f291..3813e982 100644
--- a/src/providers/twitch/TwitchAccountManager.cpp
+++ b/src/providers/twitch/TwitchAccountManager.cpp
@@ -3,11 +3,14 @@
#include "common/Common.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchCommon.hpp"
+#include "providers/twitch/api/Helix.hpp"
+#include "providers/twitch/api/Kraken.hpp"
namespace chatterino {
TwitchAccountManager::TwitchAccountManager()
- : anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
+ : accounts(SharedPtrElementLess{})
+ , anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
{
this->currentUserChanged.connect([this] {
auto currentUser = this->getCurrent();
@@ -140,6 +143,8 @@ void TwitchAccountManager::load()
if (user)
{
qDebug() << "Twitch user updated to" << newUsername;
+ getHelix()->update(user->getOAuthClient(), user->getOAuthToken());
+ getKraken()->update(user->getOAuthClient(), user->getOAuthToken());
this->currentUser_ = user;
}
else
@@ -221,7 +226,7 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
// std::lock_guard lock(this->mutex);
- this->accounts.insertItem(newUser);
+ this->accounts.insert(newUser);
return AddUserResponse::UserAdded;
}
diff --git a/src/providers/twitch/TwitchAccountManager.hpp b/src/providers/twitch/TwitchAccountManager.hpp
index c9ce0215..5956fdf0 100644
--- a/src/providers/twitch/TwitchAccountManager.hpp
+++ b/src/providers/twitch/TwitchAccountManager.hpp
@@ -51,9 +51,7 @@ public:
pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated;
- SortedSignalVector,
- SharedPtrElementLess>
- accounts;
+ SignalVector> accounts;
private:
enum class AddUserResponse {
diff --git a/src/providers/twitch/TwitchApi.cpp b/src/providers/twitch/TwitchApi.cpp
deleted file mode 100644
index 6c3b6b6e..00000000
--- a/src/providers/twitch/TwitchApi.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#include "providers/twitch/TwitchApi.hpp"
-
-#include "common/Common.hpp"
-#include "common/NetworkRequest.hpp"
-#include "providers/twitch/TwitchCommon.hpp"
-
-#include
-#include
-
-namespace chatterino {
-
-void TwitchApi::findUserId(const QString user,
- std::function successCallback)
-{
- QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
-
- NetworkRequest(requestUrl)
-
- .authorizeTwitchV5(getDefaultClientID())
- .timeout(30000)
- .onSuccess([successCallback](auto result) mutable -> Outcome {
- auto root = result.parseJson();
- if (!root.value("users").isArray())
- {
- qDebug()
- << "API Error while getting user id, users is not an array";
- successCallback("");
- return Failure;
- }
- auto users = root.value("users").toArray();
- if (users.size() != 1)
- {
- qDebug() << "API Error while getting user id, users array size "
- "is not 1";
- successCallback("");
- return Failure;
- }
- if (!users[0].isObject())
- {
- qDebug() << "API Error while getting user id, first user is "
- "not an object";
- successCallback("");
- return Failure;
- }
- auto firstUser = users[0].toObject();
- auto id = firstUser.value("_id");
- if (!id.isString())
- {
- qDebug() << "API Error: while getting user id, first user "
- "object `_id` key is not a string";
- successCallback("");
- return Failure;
- }
- successCallback(id.toString());
- return Success;
- })
- .execute();
-}
-
-void TwitchApi::findUserName(const QString userid,
- std::function successCallback)
-{
- QString requestUrl("https://api.twitch.tv/kraken/users/" + userid);
-
- NetworkRequest(requestUrl)
-
- .authorizeTwitchV5(getDefaultClientID())
- .timeout(30000)
- .onSuccess([successCallback](auto result) mutable -> Outcome {
- auto root = result.parseJson();
- auto name = root.value("name");
- if (!name.isString())
- {
- qDebug() << "API Error: while getting user name, `name` is not "
- "a string";
- successCallback("");
- return Failure;
- }
- successCallback(name.toString());
- return Success;
- })
- .execute();
-}
-
-} // namespace chatterino
diff --git a/src/providers/twitch/TwitchApi.hpp b/src/providers/twitch/TwitchApi.hpp
deleted file mode 100644
index 8b1b85c1..00000000
--- a/src/providers/twitch/TwitchApi.hpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-#include
-#include
-
-namespace chatterino {
-
-class TwitchApi
-{
-public:
- static void findUserId(const QString user,
- std::function callback);
- static void findUserName(const QString userid,
- std::function callback);
-
-private:
-};
-
-} // namespace chatterino
diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp
index d305f0c4..ac448b30 100644
--- a/src/providers/twitch/TwitchChannel.cpp
+++ b/src/providers/twitch/TwitchChannel.cpp
@@ -14,6 +14,8 @@
#include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
+#include "providers/twitch/api/Helix.hpp"
+#include "providers/twitch/api/Kraken.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Toasts.hpp"
@@ -21,6 +23,7 @@
#include "util/PostToThread.hpp"
#include "widgets/Window.hpp"
+#include
#include
#include
#include
@@ -454,35 +457,25 @@ void TwitchChannel::refreshTitle()
}
this->titleRefreshedTime_ = QTime::currentTime();
- QString url("https://api.twitch.tv/kraken/channels/" + roomID);
- NetworkRequest::twitchRequest(url)
- .onSuccess(
- [this, weak = weakOf(this)](auto result) -> Outcome {
- ChannelPtr shared = weak.lock();
- if (!shared)
- return Failure;
+ const auto onSuccess = [this,
+ weak = weakOf(this)](const auto &channel) {
+ ChannelPtr shared = weak.lock();
+ if (!shared)
+ {
+ return;
+ }
- const auto document = result.parseRapidJson();
+ {
+ auto status = this->streamStatus_.access();
+ status->title = channel.status;
+ }
- auto statusIt = document.FindMember("status");
+ this->liveStatusChanged.invoke();
+ };
- if (statusIt == document.MemberEnd())
- {
- return Failure;
- }
+ const auto onFailure = [] {};
- {
- auto status = this->streamStatus_.access();
- if (!rj::getSafe(statusIt->value, status->title))
- {
- return Failure;
- }
- }
-
- this->liveStatusChanged.invoke();
- return Success;
- })
- .execute();
+ getKraken()->getChannel(roomID, onSuccess, onFailure);
}
void TwitchChannel::refreshLiveStatus()
@@ -497,106 +490,72 @@ void TwitchChannel::refreshLiveStatus()
return;
}
- QString url("https://api.twitch.tv/kraken/streams/" + roomID);
+ getHelix()->getStreamById(
+ roomID,
+ [this, weak = weakOf(this)](bool live, const auto &stream) {
+ ChannelPtr shared = weak.lock();
+ if (!shared)
+ {
+ return;
+ }
- // auto request = makeGetStreamRequest(roomID, QThread::currentThread());
- NetworkRequest::twitchRequest(url)
-
- .onSuccess(
- [this, weak = weakOf(this)](auto result) -> Outcome {
- ChannelPtr shared = weak.lock();
- if (!shared)
- return Failure;
-
- return this->parseLiveStatus(result.parseRapidJson());
- })
- .execute();
+ this->parseLiveStatus(live, stream);
+ },
+ [] {
+ // failure
+ });
}
-Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
+void TwitchChannel::parseLiveStatus(bool live, const HelixStream &stream)
{
- if (!document.IsObject())
+ if (!live)
{
- qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
- return Failure;
- }
-
- if (!document.HasMember("stream"))
- {
- qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
- return Failure;
- }
-
- const auto &stream = document["stream"];
-
- if (!stream.IsObject())
- {
- // Stream is offline (stream is most likely null)
this->setLive(false);
- return Failure;
+ return;
}
- if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
- !stream.HasMember("channel") || !stream.HasMember("created_at"))
- {
- qDebug()
- << "[TwitchChannel:refreshLiveStatus] Missing members in stream";
- this->setLive(false);
- return Failure;
- }
-
- const rapidjson::Value &streamChannel = stream["channel"];
-
- if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
- {
- qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
- "\"status\" in channel";
- return Failure;
- }
-
- // Stream is live
-
{
auto status = this->streamStatus_.access();
- status->viewerCount = stream["viewers"].GetUint();
- status->game = stream["game"].GetString();
- status->title = streamChannel["status"].GetString();
- QDateTime since = QDateTime::fromString(
- stream["created_at"].GetString(), Qt::ISODate);
+ status->viewerCount = stream.viewerCount;
+ if (status->gameId != stream.gameId)
+ {
+ status->gameId = stream.gameId;
+
+ // Resolve game ID to game name
+ getHelix()->getGameById(
+ stream.gameId,
+ [this, weak = weakOf(this)](const auto &game) {
+ ChannelPtr shared = weak.lock();
+ if (!shared)
+ {
+ return;
+ }
+
+ {
+ auto status = this->streamStatus_.access();
+ status->game = game.name;
+ }
+
+ this->liveStatusChanged.invoke();
+ },
+ [] {
+ // failure
+ });
+ }
+ status->title = stream.title;
+ QDateTime since = QDateTime::fromString(stream.startedAt, Qt::ISODate);
auto diff = since.secsTo(QDateTime::currentDateTime());
status->uptime = QString::number(diff / 3600) + "h " +
QString::number(diff % 3600 / 60) + "m";
status->rerun = false;
- if (stream.HasMember("stream_type"))
- {
- status->streamType = stream["stream_type"].GetString();
- }
- else
- {
- status->streamType = QString();
- }
-
- if (stream.HasMember("broadcast_platform"))
- {
- const auto &broadcastPlatformValue = stream["broadcast_platform"];
-
- if (broadcastPlatformValue.IsString())
- {
- const char *broadcastPlatform =
- stream["broadcast_platform"].GetString();
- if (strcmp(broadcastPlatform, "rerun") == 0)
- {
- status->rerun = true;
- }
- }
- }
+ status->streamType = stream.type;
}
- setLive(true);
+
+ this->setLive(true);
+
// Signal all listeners that the stream status has been updated
this->liveStatusChanged.invoke();
-
- return Success;
}
void TwitchChannel::loadRecentMessages()
@@ -641,9 +600,6 @@ void TwitchChannel::loadRecentMessages()
void TwitchChannel::refreshPubsub()
{
- // listen to moderation actions
- if (!this->hasModRights())
- return;
auto roomId = this->roomId();
if (roomId.isEmpty())
return;
diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp
index 026c9071..fc40ad32 100644
--- a/src/providers/twitch/TwitchChannel.hpp
+++ b/src/providers/twitch/TwitchChannel.hpp
@@ -8,14 +8,15 @@
#include "common/UniqueAccess.hpp"
#include "common/UsernameSet.hpp"
#include "providers/twitch/TwitchEmotes.hpp"
+#include "providers/twitch/api/Helix.hpp"
-#include
#include
#include
#include
#include
-#include
#include
+
+#include
#include
namespace chatterino {
@@ -43,6 +44,7 @@ public:
unsigned viewerCount = 0;
QString title;
QString game;
+ QString gameId;
QString uptime;
QString streamType;
};
@@ -120,7 +122,7 @@ protected:
private:
// Methods
void refreshLiveStatus();
- Outcome parseLiveStatus(const rapidjson::Document &document);
+ void parseLiveStatus(bool live, const HelixStream &stream);
void refreshPubsub();
void refreshChatters();
void refreshBadges();
diff --git a/src/providers/twitch/TwitchIrcServer.cpp b/src/providers/twitch/TwitchIrcServer.cpp
index 45f50cec..0f1d4884 100644
--- a/src/providers/twitch/TwitchIrcServer.cpp
+++ b/src/providers/twitch/TwitchIrcServer.cpp
@@ -7,10 +7,8 @@
#include "common/Common.hpp"
#include "common/Env.hpp"
#include "controllers/accounts/AccountController.hpp"
-#include "controllers/highlights/HighlightController.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
-#include "providers/twitch/ChatroomChannel.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchAccount.hpp"
@@ -24,26 +22,13 @@ using namespace std::chrono_literals;
namespace chatterino {
-namespace {
- bool isChatroom(const QString &channel)
- {
- if (channel.left(10) == "chatrooms:")
- {
- auto reflist = channel.splitRef(':');
- if (reflist.size() == 3)
- {
- return true;
- }
- }
- return false;
- }
-} // namespace
-
TwitchIrcServer::TwitchIrcServer()
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
{
+ this->initializeIrc();
+
this->pubsub = new PubSub;
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
@@ -100,18 +85,8 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
std::shared_ptr TwitchIrcServer::createChannel(
const QString &channelName)
{
- std::shared_ptr channel;
- if (isChatroom(channelName))
- {
- channel = std::static_pointer_cast(
- std::shared_ptr(new ChatroomChannel(
- channelName, this->twitchBadges, this->bttv, this->ffz)));
- }
- else
- {
- channel = std::shared_ptr(new TwitchChannel(
- channelName, this->twitchBadges, this->bttv, this->ffz));
- }
+ auto channel = std::shared_ptr(new TwitchChannel(
+ channelName, this->twitchBadges, this->bttv, this->ffz));
channel->initialize();
channel->sendMessageSignal.connect(
diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp
index 0d468047..02a45a8f 100644
--- a/src/providers/twitch/TwitchMessageBuilder.cpp
+++ b/src/providers/twitch/TwitchMessageBuilder.cpp
@@ -2,9 +2,8 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
-#include "controllers/highlights/HighlightController.hpp"
#include "controllers/ignores/IgnoreController.hpp"
-#include "controllers/pings/PingController.hpp"
+#include "controllers/ignores/IgnorePhrase.hpp"
#include "messages/Message.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/twitch/TwitchBadges.hpp"
@@ -28,7 +27,8 @@
namespace {
const QSet zeroWidthEmotes{
- "SoSnowy", "IceCold", "SantaHat", "TopHat", "ReinDeer", "CandyCane",
+ "SoSnowy", "IceCold", "SantaHat", "TopHat",
+ "ReinDeer", "CandyCane", "cvMask", "cvHazmat",
};
QColor getRandomColor(const QVariant &userId)
@@ -170,12 +170,11 @@ bool TwitchMessageBuilder::isIgnored() const
auto app = getApp();
// TODO(pajlada): Do we need to check if the phrase is valid first?
- for (const auto &phrase : app->ignores->phrases)
+ auto phrases = getCSettings().ignoredMessages.readOnly();
+ for (const auto &phrase : *phrases)
{
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
{
- qDebug() << "Blocking message because it contains ignored phrase"
- << phrase.getPattern();
return true;
}
}
@@ -205,8 +204,7 @@ bool TwitchMessageBuilder::isIgnored() const
case ShowIgnoredUsersMessages::Never:
break;
}
- qDebug() << "Blocking message because it's from blocked user"
- << user.name;
+
return true;
}
}
@@ -238,7 +236,7 @@ void TwitchMessageBuilder::triggerHighlights()
return;
}
- if (getApp()->pings->isMuted(this->channel->getName()))
+ if (getCSettings().isMutedChannel(this->channel->getName()))
{
// Do nothing. Pings are muted in this channel.
return;
@@ -297,6 +295,13 @@ MessagePtr TwitchMessageBuilder::build()
this->historicalMessage_ = this->tags.contains("historical");
+ if (this->tags.contains("msg-id") &&
+ this->tags["msg-id"].toString().split(';').contains(
+ "highlighted-message"))
+ {
+ this->message().flags.set(MessageFlag::RedeemedHighlight);
+ }
+
// timestamp
if (this->historicalMessage_)
{
@@ -764,8 +769,7 @@ void TwitchMessageBuilder::appendUsername()
void TwitchMessageBuilder::runIgnoreReplaces(
std::vector> &twitchEmotes)
{
- auto app = getApp();
- const auto &phrases = app->ignores->phrases;
+ auto phrases = getCSettings().ignoredMessages.readOnly();
auto removeEmotesInRange =
[](int pos, int len,
std::vector>
@@ -828,7 +832,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
}
};
- for (const auto &phrase : phrases)
+ for (const auto &phrase : *phrases)
{
if (phrase.isBlock())
{
@@ -1017,7 +1021,7 @@ void TwitchMessageBuilder::parseHighlights()
QString currentUsername = currentUser->getUserName();
- if (app->highlights->blacklistContains(this->ircMessage->nick()))
+ if (getCSettings().isBlacklistedUser(this->ircMessage->nick()))
{
// Do nothing. We ignore highlights from this user.
return;
@@ -1056,18 +1060,14 @@ void TwitchMessageBuilder::parseHighlights()
*/
}
- std::vector userHighlights =
- app->highlights->highlightedUsers.cloneVector();
-
// Highlight because of sender
- for (const HighlightPhrase &userHighlight : userHighlights)
+ auto userHighlights = getCSettings().highlightedUsers.readOnly();
+ for (const HighlightPhrase &userHighlight : *userHighlights)
{
if (!userHighlight.isMatch(this->ircMessage->nick()))
{
continue;
}
- qDebug() << "Highlight because user" << this->ircMessage->nick()
- << "sent a message";
this->message().flags.set(MessageFlag::Highlighted);
this->message().highlightColor = userHighlight.getColor();
@@ -1111,7 +1111,7 @@ void TwitchMessageBuilder::parseHighlights()
// TODO: This vector should only be rebuilt upon highlights being changed
// fourtf: should be implemented in the HighlightsController
std::vector activeHighlights =
- app->highlights->phrases.cloneVector();
+ getSettings()->highlightedMessages.cloneVector();
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0)
{
@@ -1131,9 +1131,6 @@ void TwitchMessageBuilder::parseHighlights()
continue;
}
- qDebug() << "Highlight because" << this->originalMessage_ << "matches"
- << highlight.getPattern();
-
this->message().flags.set(MessageFlag::Highlighted);
this->message().highlightColor = highlight.getColor();
diff --git a/src/providers/twitch/TwitchParseCheerEmotes.cpp b/src/providers/twitch/TwitchParseCheerEmotes.cpp
index 5258baf9..9667ffd2 100644
--- a/src/providers/twitch/TwitchParseCheerEmotes.cpp
+++ b/src/providers/twitch/TwitchParseCheerEmotes.cpp
@@ -278,6 +278,7 @@ namespace {
return true;
}
+
} // namespace
// Look through the results of
diff --git a/src/providers/twitch/api/Helix.cpp b/src/providers/twitch/api/Helix.cpp
new file mode 100644
index 00000000..974b6212
--- /dev/null
+++ b/src/providers/twitch/api/Helix.cpp
@@ -0,0 +1,368 @@
+#include "providers/twitch/api/Helix.hpp"
+
+#include "common/Outcome.hpp"
+
+namespace chatterino {
+
+static Helix *instance = nullptr;
+
+void Helix::fetchUsers(QStringList userIds, QStringList userLogins,
+ ResultCallback> successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QUrlQuery urlQuery;
+
+ for (const auto &id : userIds)
+ {
+ urlQuery.addQueryItem("id", id);
+ }
+
+ for (const auto &login : userLogins)
+ {
+ urlQuery.addQueryItem("login", login);
+ }
+
+ // TODO: set on success and on error
+ this->makeRequest("users", urlQuery)
+ .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
+ auto root = result.parseJson();
+ auto data = root.value("data");
+
+ if (!data.isArray())
+ {
+ failureCallback();
+ return Failure;
+ }
+
+ std::vector users;
+
+ for (const auto &jsonUser : data.toArray())
+ {
+ users.emplace_back(jsonUser.toObject());
+ }
+
+ successCallback(users);
+
+ return Success;
+ })
+ .onError([failureCallback](auto result) {
+ // TODO: make better xd
+ failureCallback();
+ })
+ .execute();
+}
+
+void Helix::getUserByName(QString userId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QStringList userIds;
+ QStringList userLogins{userId};
+
+ this->fetchUsers(
+ userIds, userLogins,
+ [successCallback,
+ failureCallback](const std::vector &users) {
+ if (users.empty())
+ {
+ failureCallback();
+ return;
+ }
+ successCallback(users[0]);
+ },
+ failureCallback);
+}
+
+void Helix::getUserById(QString userId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QStringList userIds{userId};
+ QStringList userLogins;
+
+ this->fetchUsers(
+ userIds, userLogins,
+ [successCallback, failureCallback](const auto &users) {
+ if (users.empty())
+ {
+ failureCallback();
+ return;
+ }
+ successCallback(users[0]);
+ },
+ failureCallback);
+}
+
+void Helix::fetchUsersFollows(
+ QString fromId, QString toId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ assert(!fromId.isEmpty() || !toId.isEmpty());
+
+ QUrlQuery urlQuery;
+
+ if (!fromId.isEmpty())
+ {
+ urlQuery.addQueryItem("from_id", fromId);
+ }
+
+ if (!toId.isEmpty())
+ {
+ urlQuery.addQueryItem("to_id", toId);
+ }
+
+ // TODO: set on success and on error
+ this->makeRequest("users/follows", urlQuery)
+ .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
+ auto root = result.parseJson();
+ if (root.empty())
+ {
+ failureCallback();
+ return Failure;
+ }
+ successCallback(HelixUsersFollowsResponse(root));
+ return Success;
+ })
+ .onError([failureCallback](auto result) {
+ // TODO: make better xd
+ failureCallback();
+ })
+ .execute();
+}
+
+void Helix::getUserFollowers(
+ QString userId, ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ this->fetchUsersFollows("", userId, successCallback, failureCallback);
+}
+
+void Helix::getUserFollow(
+ QString userId, QString targetId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ this->fetchUsersFollows(
+ userId, targetId,
+ [successCallback](const auto &response) {
+ if (response.data.empty())
+ {
+ successCallback(false, HelixUsersFollowsRecord());
+ return;
+ }
+
+ successCallback(true, response.data[0]);
+ },
+ failureCallback);
+}
+
+void Helix::fetchStreams(
+ QStringList userIds, QStringList userLogins,
+ ResultCallback> successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QUrlQuery urlQuery;
+
+ for (const auto &id : userIds)
+ {
+ urlQuery.addQueryItem("user_id", id);
+ }
+
+ for (const auto &login : userLogins)
+ {
+ urlQuery.addQueryItem("user_login", login);
+ }
+
+ // TODO: set on success and on error
+ this->makeRequest("streams", urlQuery)
+ .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
+ auto root = result.parseJson();
+ auto data = root.value("data");
+
+ if (!data.isArray())
+ {
+ failureCallback();
+ return Failure;
+ }
+
+ std::vector streams;
+
+ for (const auto &jsonStream : data.toArray())
+ {
+ streams.emplace_back(jsonStream.toObject());
+ }
+
+ successCallback(streams);
+
+ return Success;
+ })
+ .onError([failureCallback](auto result) {
+ // TODO: make better xd
+ failureCallback();
+ })
+ .execute();
+}
+
+void Helix::getStreamById(QString userId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QStringList userIds{userId};
+ QStringList userLogins;
+
+ this->fetchStreams(
+ userIds, userLogins,
+ [successCallback, failureCallback](const auto &streams) {
+ if (streams.empty())
+ {
+ successCallback(false, HelixStream());
+ return;
+ }
+ successCallback(true, streams[0]);
+ },
+ failureCallback);
+}
+
+void Helix::getStreamByName(QString userName,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QStringList userIds;
+ QStringList userLogins{userName};
+
+ this->fetchStreams(
+ userIds, userLogins,
+ [successCallback, failureCallback](const auto &streams) {
+ if (streams.empty())
+ {
+ successCallback(false, HelixStream());
+ return;
+ }
+ successCallback(true, streams[0]);
+ },
+ failureCallback);
+}
+
+///
+
+void Helix::fetchGames(QStringList gameIds, QStringList gameNames,
+ ResultCallback> successCallback,
+ HelixFailureCallback failureCallback)
+{
+ assert((gameIds.length() + gameNames.length()) > 0);
+
+ QUrlQuery urlQuery;
+
+ for (const auto &id : gameIds)
+ {
+ urlQuery.addQueryItem("id", id);
+ }
+
+ for (const auto &login : gameNames)
+ {
+ urlQuery.addQueryItem("name", login);
+ }
+
+ // TODO: set on success and on error
+ this->makeRequest("games", urlQuery)
+ .onSuccess([successCallback, failureCallback](auto result) -> Outcome {
+ auto root = result.parseJson();
+ auto data = root.value("data");
+
+ if (!data.isArray())
+ {
+ failureCallback();
+ return Failure;
+ }
+
+ std::vector games;
+
+ for (const auto &jsonStream : data.toArray())
+ {
+ games.emplace_back(jsonStream.toObject());
+ }
+
+ successCallback(games);
+
+ return Success;
+ })
+ .onError([failureCallback](auto result) {
+ // TODO: make better xd
+ failureCallback();
+ })
+ .execute();
+}
+
+void Helix::getGameById(QString gameId,
+ ResultCallback successCallback,
+ HelixFailureCallback failureCallback)
+{
+ QStringList gameIds{gameId};
+ QStringList gameNames;
+
+ this->fetchGames(
+ gameIds, gameNames,
+ [successCallback, failureCallback](const auto &games) {
+ if (games.empty())
+ {
+ failureCallback();
+ return;
+ }
+ successCallback(games[0]);
+ },
+ failureCallback);
+}
+
+NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
+{
+ assert(!url.startsWith("/"));
+
+ if (this->clientId.isEmpty())
+ {
+ qDebug()
+ << "Helix::makeRequest called without a client ID set BabyRage";
+ // return boost::none;
+ }
+
+ if (this->oauthToken.isEmpty())
+ {
+ qDebug()
+ << "Helix::makeRequest called without an oauth token set BabyRage";
+ // return boost::none;
+ }
+
+ const QString baseUrl("https://api.twitch.tv/helix/");
+
+ QUrl fullUrl(baseUrl + url);
+
+ fullUrl.setQuery(urlQuery);
+
+ return NetworkRequest(fullUrl)
+ .timeout(5 * 1000)
+ .header("Accept", "application/json")
+ .header("Client-ID", this->clientId)
+ .header("Authorization", "Bearer " + this->oauthToken);
+}
+
+void Helix::update(QString clientId, QString oauthToken)
+{
+ this->clientId = clientId;
+ this->oauthToken = oauthToken;
+}
+
+void Helix::initialize()
+{
+ assert(instance == nullptr);
+
+ instance = new Helix();
+}
+
+Helix *getHelix()
+{
+ assert(instance != nullptr);
+
+ return instance;
+}
+
+} // namespace chatterino
diff --git a/src/providers/twitch/api/Helix.hpp b/src/providers/twitch/api/Helix.hpp
new file mode 100644
index 00000000..bfe54b69
--- /dev/null
+++ b/src/providers/twitch/api/Helix.hpp
@@ -0,0 +1,198 @@
+#pragma once
+
+#include "common/NetworkRequest.hpp"
+
+#include
+#include
+#include