diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 000000000..9880f2ce1
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,3 @@
+# Description
+
+
diff --git a/.gitmodules b/.gitmodules
index 45c84aee1..da905b54d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -17,4 +17,4 @@
[submodule "lib/appbase"]
path = lib/appbase
- url = https://github.com/fourtf/appbase
+ url = https://github.com/Chatterino/appbase
diff --git a/.travis.yml b/.travis.yml
index 1267bf14c..1ad6dc6e7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,35 @@
-before_install:
- - sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
- - sudo apt-get update -qq
- - sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
- - sudo apt-get install qt5-default qttools5-dev-tools
+os: osx
+osx_image: xcode10.2
+
+addons:
+ homebrew:
+ packages:
+ - boost
+ - openssl
+ - rapidjson
+ - qt
+ - p7zip
+
+compiler: clang
script:
- - qmake -qt=qt5 -v
- - qmake -qt=qt5
- - make
+ - mkdir build && cd build
+ - /usr/local/opt/qt/bin/qmake .. && make -j8
+ - /usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
+ - mv chatterino.dmg chatterino-osx.dmg
+
+before_deploy:
+ - git config --global user.email "builds@travis-ci.com"
+ - git config --global user.name "Travis CI"
+ - export GIT_TAG=nightly-build
+ - git tag $GIT_TAG -f
+
+deploy:
+ skip_cleanup: true
+ provider: releases
+ api_key:
+ secure: ZzS55wlwtLAVEBaDDMqiuqZwuTpvLbNnaNw0enfiqpjWT7hgbbp/SBw2rbYIkVqm7tBHCLnEzKto6p4Gz6ROo0gGACARmx7EwIloX18rMCuBWygNHRyVruDSlmEOLWRqYByDbUdCkKhYr9aegnkm7zhzCmSBCTW28/uVlxM2bTHIgqKEpB4k1W8OqKdJDxqZKeF4r7nDNSOx5ylhpiK+WNFK8yfiaF1SQlSwsdv9o1RkbJlew7iigvHvEM2kDMkiMWYlJ2khkUWVCVQDQGe4/ya5pgTIHDLu5sZuclp5zhgfDf1U3STvsbQWvxJfsmCId7IQHJ83OSFeoUf6y849i3GMqlNi3aXrxEx0fi0dILQ76/Sj246FPMA4kC0/W49uaxqD784wFuJDjSWeWwi/NPoJ/gz0mGZy+08BoztOGqqOKjJJdESBYTio71N8VcK09zQ0LjXRmX+g3BbrK6a2F3hiMKeuYwdaN2/KdMMoqFDau6L3fXLdpcHKdJC8K/yzJtyyIe0CRB2nj8sZLHfxDwoRm7gOTDXq1zPL7CP9cCwCnCR6nm3CqUW/CnSWuMKpSoQRlP5EBI7zzYT2/tZc/vat5nob7Xif6yFF9fh/VHx4tC6zsfkA1nPPN3+QpdVInRo7dCVxtTqey5FdVjSiv7n11TrFhZ7+Fr5x6CZqa58=
+ file: "chatterino-osx.dmg"
+ prerelease: true
+ on:
+ branch: nightly
diff --git a/BUILDING_ON_LINUX.md b/BUILDING_ON_LINUX.md
index f594e2c10..0c717d8b6 100644
--- a/BUILDING_ON_LINUX.md
+++ b/BUILDING_ON_LINUX.md
@@ -4,7 +4,7 @@ Note on Qt version compatibility: If you are installing Qt from a package manage
## Ubuntu 18.04
*most likely works the same for other Debian-like distros*
-1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev`
+1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev`
1. Open `chatterino.pro` with QT Creator and build
## Arch Linux
diff --git a/BUILDING_ON_WINDOWS.md b/BUILDING_ON_WINDOWS.md
index 7aedb0cab..40df39bb0 100644
--- a/BUILDING_ON_WINDOWS.md
+++ b/BUILDING_ON_WINDOWS.md
@@ -102,7 +102,14 @@ To produce all supplement files for a standalone build, follow these steps (adju
cd C:\Users\example\src\build-chatterino-Desktop_Qt_5_11_2_MSVC2017_64bit-Release\release
C:\Qt\5.11.2\msvc2017_64\bin\windeployqt.exe chatterino.exe
+
+ 5. Go to `C:\local\bin\` and copy these dll's into your `release folder`.
+
+ libssl-1_1-x64.dll
+ libcrypto-1_1-x64.dll
+ ssleay32.dll
+ libeay32.dll
- 5. The `releases` directory will now be populated with all the required files to make the chatterino build standalone.
+ 6. The `releases` directory will now be populated with all the required files to make the chatterino build standalone.
You can now create a zip archive of all the contents in `releases` and distribute the program as is, without requiring any development tools to be present on the target system. (However, the vcredist package must be present, as usual - see the [README](README.md)).
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 000000000..4bcd983a4
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.8)
+
+project(chatterino)
+
+include_directories(src)
+
+set(chatterino_SOURCES
+ src/common/UsernameSet.cpp
+ )
+
+find_package(Qt5Widgets CONFIG REQUIRED)
+find_package(Qt5 5.9.0 REQUIRED COMPONENTS
+ Core
+ )
+
+# set(CMAKE_AUTOMOC ON)
+
+if (BUILD_TESTS)
+ message("++ Tests enabled")
+ find_package(GTest)
+ enable_testing()
+
+ add_executable(chatterino-test
+ ${chatterino_SOURCES}
+
+ tests/src/main.cpp
+ tests/src/UsernameSet.cpp
+ )
+
+ target_link_libraries(chatterino-test Qt5::Core)
+
+ target_link_libraries(chatterino-test gtest gtest_main)
+
+ gtest_discover_tests(chatterino-test)
+else()
+ message(FATAL_ERROR "This cmake file is only intended for tests right now. Use qmake to build chatterino2")
+endif()
diff --git a/README.md b/README.md
index d35238e83..defff3340 100644
--- a/README.md
+++ b/README.md
@@ -43,3 +43,4 @@ The code is formated using clang format in Qt Creator. [.clang-format](https://g
7. Under `Clang Format` select `Use predefined style: File` and `Fallback style: None`
Qt creator should now format the documents when saving it.
+
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 000000000..a589baed7
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,65 @@
+version: 1.0.{build}
+branches:
+ only:
+ - nightly
+image: Visual Studio 2017
+platform: Any CPU
+clone_depth: 1
+init:
+- cmd: ''
+install:
+- cmd: >-
+ git submodule update --init --recursive
+
+ set QTDIR=C:\Qt\5.11\msvc2017_64
+
+ set PATH=%PATH%;%QTDIR%\bin
+
+ call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
+build_script:
+- cmd: >-
+ curl -fsS -o openssl.7z https://pajlada.se/files/openssl.7z
+
+ 7z x openssl.7z
+
+ dir
+
+ mkdir build
+
+ cd build
+
+ qmake ../chatterino.pro BOOST_DIRECTORY="C:\Libraries\boost_1_64_0" BOOST_LIB_SUFFIX="lib64-msvc-14.1" OPENSSL_DIRECTORY="%APPVEYOR_BUILD_FOLDER%\openssl" DEFINES+="CHATTERINO_NIGHTLY_VERSION_STRING=\\\"$$system(git describe --always)-$$system(git rev-list master --count)\\\""
+
+ set cl=/MP
+
+ nmake /S /NOLOGO
+
+ git clone https://github.com/pajlada/chatterino2-dlls.git
+
+ mkdir Chatterino2
+
+ cp ../openssl/bin/libcrypto*.dll ../openssl/bin/libssl*.dll Chatterino2/
+
+ cp chatterino2-dlls/*.dll Chatterino2/
+
+ windeployqt release/chatterino.exe --release --no-compiler-runtime --no-translations --no-opengl-sw --dir Chatterino2/
+
+ cp release/chatterino.exe Chatterino2/
+
+ 7z a chatterino-windows-x86-64.zip Chatterino2/
+artifacts:
+- path: build/chatterino-windows-x86-64.zip
+ name: chatterino
+deploy:
+- provider: GitHub
+ tag: nightly-build
+ release: nightly-build
+ description: 'nightly v$(appveyor_build_version) built $(APPVEYOR_REPO_COMMIT_TIMESTAMP)\nLast change: $(APPVEYOR_REPO_COMMIT_MESSAGE) \n$(APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED)'
+ auth_token:
+ secure: sAJzAbiQSsYZLT+byDar9u61X0E9o35anaPMSFkOzdHeDFHjx1kW4cDP/4EEbxhx
+ repository: Chatterino/chatterino2
+ artifact: build/chatterino-windows-x86-64.zip
+ prerelease: true
+ force_update: true
+ on:
+ branch: nightly
diff --git a/chatterino.pro b/chatterino.pro
index dafca10d3..56517ef3c 100644
--- a/chatterino.pro
+++ b/chatterino.pro
@@ -1,403 +1,424 @@
-#-------------------------------------------------
-#
-# Project created by QtCreator 2016-12-28T18:23:35
-#
-#-------------------------------------------------
-
-message(----)
-
-# define project shit
-QT += widgets core gui network multimedia svg concurrent
-CONFIG += communi
-COMMUNI += core model util
-
-INCLUDEPATH += src/
-TARGET = chatterino
-TEMPLATE = app
-PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
-CONFIG += precompile_header
-DEFINES += CHATTERINO
-DEFINES += "AB_NAMESPACE=chatterino"
-DEFINES += AB_CUSTOM_THEME
-DEFINES += AB_CUSTOM_SETTINGS
-CONFIG += AB_NOT_STANDALONE
-
-useBreakpad {
- LIBS += -L$$PWD/lib/qBreakpad/handler/build
- include(lib/qBreakpad/qBreakpad.pri)
- DEFINES += C_USE_BREAKPAD
-}
-
-# https://bugreports.qt.io/browse/QTBUG-27018
-equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
- TARGET = bin/chatterino
-}
-
-# Icons
-#macx:ICON = resources/images/chatterino2.icns
-win32:RC_FILE = resources/windows.rc
-
-macx {
- LIBS += -L/usr/local/lib
-}
-
-# Submodules
-include(lib/appbase.pri)
-include(lib/humanize.pri)
-DEFINES += IRC_NAMESPACE=Communi
-include(lib/libcommuni.pri)
-include(lib/websocketpp.pri)
-include(lib/openssl.pri)
-include(lib/wintoast.pri)
-
-# Optional feature: QtWebEngine
-#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
-# message(Using QWebEngine)
-# QT += webenginewidgets
-# DEFINES += "USEWEBENGINE"
-#}
-
-SOURCES += \
- src/Application.cpp \
- src/common/Channel.cpp \
- src/common/CompletionModel.cpp \
- src/common/NetworkData.cpp \
- src/common/NetworkManager.cpp \
- src/common/NetworkRequest.cpp \
- src/common/NetworkResult.cpp \
- src/common/NetworkTimer.cpp \
- src/controllers/accounts/Account.cpp \
- src/controllers/accounts/AccountController.cpp \
- src/controllers/accounts/AccountModel.cpp \
- src/controllers/commands/Command.cpp \
- src/controllers/commands/CommandController.cpp \
- src/controllers/commands/CommandModel.cpp \
- src/controllers/highlights/HighlightController.cpp \
- src/controllers/highlights/HighlightModel.cpp \
- src/controllers/highlights/HighlightBlacklistModel.cpp \
- src/controllers/highlights/UserHighlightModel.cpp \
- src/controllers/ignores/IgnoreController.cpp \
- src/controllers/ignores/IgnoreModel.cpp \
- src/controllers/notifications/NotificationController.cpp \
- src/controllers/taggedusers/TaggedUser.cpp \
- src/controllers/taggedusers/TaggedUsersController.cpp \
- src/controllers/taggedusers/TaggedUsersModel.cpp \
- src/main.cpp \
- src/messages/Image.cpp \
- src/messages/layouts/MessageLayout.cpp \
- src/messages/layouts/MessageLayoutContainer.cpp \
- src/messages/layouts/MessageLayoutElement.cpp \
- src/messages/Link.cpp \
- src/messages/Message.cpp \
- src/messages/MessageBuilder.cpp \
- src/messages/MessageColor.cpp \
- src/messages/MessageElement.cpp \
- src/providers/emoji/Emojis.cpp \
- src/providers/irc/AbstractIrcServer.cpp \
- src/providers/irc/IrcAccount.cpp \
- src/providers/irc/IrcChannel2.cpp \
- src/providers/irc/IrcConnection2.cpp \
- src/providers/irc/IrcServer.cpp \
- src/providers/twitch/IrcMessageHandler.cpp \
- src/providers/twitch/PartialTwitchUser.cpp \
- src/providers/twitch/PubsubActions.cpp \
- src/providers/twitch/PubsubHelpers.cpp \
- src/providers/twitch/TwitchAccount.cpp \
- src/providers/twitch/TwitchAccountManager.cpp \
- src/providers/twitch/TwitchChannel.cpp \
- src/providers/twitch/TwitchEmotes.cpp \
- src/providers/twitch/TwitchHelpers.cpp \
- src/providers/twitch/TwitchMessageBuilder.cpp \
- src/providers/twitch/TwitchServer.cpp \
- src/providers/twitch/TwitchUser.cpp \
- src/singletons/helper/GifTimer.cpp \
- src/singletons/helper/LoggingChannel.cpp \
- src/controllers/moderationactions/ModerationAction.cpp \
- src/singletons/WindowManager.cpp \
- src/util/DebugCount.cpp \
- src/util/RapidjsonHelpers.cpp \
- src/util/StreamLink.cpp \
- src/widgets/AccountSwitchPopupWidget.cpp \
- src/widgets/AccountSwitchWidget.cpp \
- src/widgets/AttachedWindow.cpp \
- src/widgets/dialogs/EmotePopup.cpp \
- src/widgets/dialogs/LastRunCrashDialog.cpp \
- src/widgets/dialogs/LoginDialog.cpp \
- src/widgets/dialogs/LogsPopup.cpp \
- src/widgets/dialogs/NotificationPopup.cpp \
- src/widgets/dialogs/QualityPopup.cpp \
- src/widgets/dialogs/SelectChannelDialog.cpp \
- src/widgets/dialogs/SettingsDialog.cpp \
- src/widgets/dialogs/TextInputDialog.cpp \
- src/widgets/dialogs/UserInfoPopup.cpp \
- src/widgets/dialogs/WelcomeDialog.cpp \
- src/widgets/helper/ChannelView.cpp \
- src/widgets/helper/ComboBoxItemDelegate.cpp \
- src/widgets/helper/DebugPopup.cpp \
- src/widgets/helper/EditableModelView.cpp \
- src/widgets/helper/NotebookButton.cpp \
- src/widgets/helper/NotebookTab.cpp \
- src/widgets/helper/ResizingTextEdit.cpp \
- src/widgets/helper/ScrollbarHighlight.cpp \
- src/widgets/helper/SearchPopup.cpp \
- src/widgets/helper/SettingsDialogTab.cpp \
- src/widgets/Notebook.cpp \
- src/widgets/Scrollbar.cpp \
- src/widgets/settingspages/AboutPage.cpp \
- src/widgets/settingspages/AccountsPage.cpp \
- src/widgets/settingspages/BrowserExtensionPage.cpp \
- src/widgets/settingspages/CommandPage.cpp \
- src/widgets/settingspages/EmotesPage.cpp \
- src/widgets/settingspages/ExternalToolsPage.cpp \
- src/widgets/settingspages/HighlightingPage.cpp \
- src/widgets/settingspages/KeyboardSettingsPage.cpp \
- src/widgets/settingspages/LogsPage.cpp \
- src/widgets/settingspages/ModerationPage.cpp \
- src/widgets/settingspages/NotificationPage.cpp \
- src/widgets/settingspages/SettingsPage.cpp \
- src/widgets/settingspages/SpecialChannelsPage.cpp \
- src/widgets/splits/Split.cpp \
- src/widgets/splits/SplitContainer.cpp \
- src/widgets/splits/SplitHeader.cpp \
- src/widgets/splits/SplitInput.cpp \
- src/widgets/splits/SplitOverlay.cpp \
- src/widgets/StreamView.cpp \
- src/widgets/Window.cpp \
- src/common/LinkParser.cpp \
- src/controllers/moderationactions/ModerationActions.cpp \
- src/singletons/NativeMessaging.cpp \
- src/singletons/Emotes.cpp \
- src/singletons/Logging.cpp \
- src/singletons/Paths.cpp \
- src/singletons/Resources.cpp \
- src/singletons/Settings.cpp \
- src/singletons/Updates.cpp \
- src/singletons/Theme.cpp \
- src/controllers/moderationactions/ModerationActionModel.cpp \
- src/widgets/settingspages/LookPage.cpp \
- src/widgets/settingspages/FeelPage.cpp \
- src/util/InitUpdateButton.cpp \
- src/widgets/dialogs/UpdateDialog.cpp \
- src/widgets/settingspages/IgnoresPage.cpp \
- src/providers/twitch/PubsubClient.cpp \
- src/providers/twitch/TwitchApi.cpp \
- src/messages/Emote.cpp \
- src/messages/ImageSet.cpp \
- src/providers/bttv/BttvEmotes.cpp \
- src/providers/LinkResolver.cpp \
- src/providers/ffz/FfzEmotes.cpp \
- src/autogenerated/ResourcesAutogen.cpp \
- src/singletons/Badges.cpp \
- src/providers/twitch/TwitchBadges.cpp \
- src/providers/chatterino/ChatterinoBadges.cpp \
- src/providers/twitch/TwitchParseCheerEmotes.cpp \
- src/providers/bttv/LoadBttvChannelEmote.cpp \
- src/util/JsonQuery.cpp \
- src/RunGui.cpp \
- src/BrowserExtension.cpp \
- src/util/FormatTime.cpp \
- src/controllers/notifications/NotificationModel.cpp \
- src/singletons/Toasts.cpp \
- src/common/DownloadManager.cpp \
- src/messages/MessageContainer.cpp \
- src/common/UsernameSet.cpp \
- src/widgets/settingspages/AdvancedPage.cpp \
- src/util/IncognitoBrowser.cpp \
- src/widgets/splits/ClosedSplits.cpp \
- src/providers/ffz/FfzModBadge.cpp \
- src/widgets/settingspages/GeneralPage.cpp \
- src/providers/twitch/ChatroomChannel.cpp
-
-HEADERS += \
- src/Application.hpp \
- src/common/Channel.hpp \
- src/common/Common.hpp \
- src/common/CompletionModel.hpp \
- src/common/Atomic.hpp \
- src/common/NetworkCommon.hpp \
- src/common/NetworkData.hpp \
- src/common/NetworkManager.hpp \
- src/common/NetworkRequest.hpp \
- src/common/NetworkRequester.hpp \
- src/common/NetworkResult.hpp \
- src/common/NetworkTimer.hpp \
- src/common/NetworkWorker.hpp \
- src/common/NullablePtr.hpp \
- src/common/ProviderId.hpp \
- src/common/SignalVectorModel.hpp \
- src/common/Version.hpp \
- src/controllers/accounts/Account.hpp \
- src/controllers/accounts/AccountController.hpp \
- src/controllers/accounts/AccountModel.hpp \
- src/controllers/commands/Command.hpp \
- src/controllers/commands/CommandController.hpp \
- src/controllers/commands/CommandModel.hpp \
- src/controllers/highlights/HighlightController.hpp \
- src/controllers/highlights/HighlightModel.hpp \
- src/controllers/highlights/HighlightBlacklistModel.hpp \
- src/controllers/highlights/HighlightPhrase.hpp \
- src/controllers/highlights/HighlightBlacklistUser.hpp \
- src/controllers/highlights/UserHighlightModel.hpp \
- src/controllers/ignores/IgnoreController.hpp \
- src/controllers/ignores/IgnoreModel.hpp \
- src/controllers/ignores/IgnorePhrase.hpp \
- src/controllers/notifications/NotificationController.hpp \
- src/controllers/taggedusers/TaggedUser.hpp \
- src/controllers/taggedusers/TaggedUsersController.hpp \
- src/controllers/taggedusers/TaggedUsersModel.hpp \
- src/messages/Image.hpp \
- src/messages/layouts/MessageLayout.hpp \
- src/messages/layouts/MessageLayoutContainer.hpp \
- src/messages/layouts/MessageLayoutElement.hpp \
- src/messages/LimitedQueue.hpp \
- src/messages/LimitedQueueSnapshot.hpp \
- src/messages/Link.hpp \
- src/messages/Message.hpp \
- src/messages/MessageBuilder.hpp \
- src/messages/MessageColor.hpp \
- src/messages/MessageElement.hpp \
- src/messages/Selection.hpp \
- src/PrecompiledHeader.hpp \
- src/providers/emoji/Emojis.hpp \
- src/providers/irc/AbstractIrcServer.hpp \
- src/providers/irc/IrcAccount.hpp \
- src/providers/irc/IrcChannel2.hpp \
- src/providers/irc/IrcConnection2.hpp \
- src/providers/irc/IrcServer.hpp \
- src/providers/twitch/EmoteValue.hpp \
- src/providers/twitch/IrcMessageHandler.hpp \
- src/providers/twitch/PartialTwitchUser.hpp \
- src/providers/twitch/PubsubActions.hpp \
- src/providers/twitch/PubsubHelpers.hpp \
- src/providers/twitch/TwitchAccount.hpp \
- src/providers/twitch/TwitchAccountManager.hpp \
- src/providers/twitch/TwitchChannel.hpp \
- src/providers/twitch/TwitchEmotes.hpp \
- src/providers/twitch/TwitchHelpers.hpp \
- src/providers/twitch/TwitchMessageBuilder.hpp \
- src/providers/twitch/TwitchServer.hpp \
- src/providers/twitch/TwitchUser.hpp \
- src/singletons/helper/GifTimer.hpp \
- src/singletons/helper/LoggingChannel.hpp \
- src/controllers/moderationactions/ModerationAction.hpp \
- src/singletons/WindowManager.hpp \
- src/util/ConcurrentMap.hpp \
- src/util/DebugCount.hpp \
- src/util/IrcHelpers.hpp \
- src/util/LayoutCreator.hpp \
- src/util/QStringHash.hpp \
- src/util/RapidjsonHelpers.hpp \
- src/util/RemoveScrollAreaBackground.hpp \
- src/util/SharedPtrElementLess.hpp \
- src/util/StandardItemHelper.hpp \
- src/util/StreamLink.hpp \
- src/widgets/AccountSwitchPopupWidget.hpp \
- src/widgets/AccountSwitchWidget.hpp \
- src/widgets/AttachedWindow.hpp \
- src/widgets/dialogs/EmotePopup.hpp \
- src/widgets/dialogs/LastRunCrashDialog.hpp \
- src/widgets/dialogs/LoginDialog.hpp \
- src/widgets/dialogs/LogsPopup.hpp \
- src/widgets/dialogs/NotificationPopup.hpp \
- src/widgets/dialogs/QualityPopup.hpp \
- src/widgets/dialogs/SelectChannelDialog.hpp \
- src/widgets/dialogs/SettingsDialog.hpp \
- src/widgets/dialogs/TextInputDialog.hpp \
- src/widgets/dialogs/UserInfoPopup.hpp \
- src/widgets/dialogs/WelcomeDialog.hpp \
- src/widgets/helper/ChannelView.hpp \
- src/widgets/helper/ComboBoxItemDelegate.hpp \
- src/widgets/helper/DebugPopup.hpp \
- src/widgets/helper/EditableModelView.hpp \
- src/widgets/helper/Line.hpp \
- src/widgets/helper/NotebookButton.hpp \
- src/widgets/helper/NotebookTab.hpp \
- src/widgets/helper/ResizingTextEdit.hpp \
- src/widgets/helper/ScrollbarHighlight.hpp \
- src/widgets/helper/SearchPopup.hpp \
- src/widgets/helper/SettingsDialogTab.hpp \
- src/widgets/Notebook.hpp \
- src/widgets/Scrollbar.hpp \
- src/widgets/settingspages/AboutPage.hpp \
- src/widgets/settingspages/AccountsPage.hpp \
- src/widgets/settingspages/BrowserExtensionPage.hpp \
- src/widgets/settingspages/CommandPage.hpp \
- src/widgets/settingspages/EmotesPage.hpp \
- src/widgets/settingspages/ExternalToolsPage.hpp \
- src/widgets/settingspages/HighlightingPage.hpp \
- src/widgets/settingspages/KeyboardSettingsPage.hpp \
- src/widgets/settingspages/LogsPage.hpp \
- src/widgets/settingspages/ModerationPage.hpp \
- src/widgets/settingspages/NotificationPage.hpp \
- src/widgets/settingspages/SettingsPage.hpp \
- src/widgets/settingspages/SpecialChannelsPage.hpp \
- src/widgets/splits/Split.hpp \
- src/widgets/splits/SplitContainer.hpp \
- src/widgets/splits/SplitHeader.hpp \
- src/widgets/splits/SplitInput.hpp \
- src/widgets/splits/SplitOverlay.hpp \
- src/widgets/StreamView.hpp \
- src/widgets/Window.hpp \
- src/providers/twitch/TwitchCommon.hpp \
- src/util/IsBigEndian.hpp \
- src/common/LinkParser.hpp \
- src/controllers/moderationactions/ModerationActions.hpp \
- src/singletons/Emotes.hpp \
- src/singletons/Logging.hpp \
- src/singletons/Paths.hpp \
- src/singletons/Resources.hpp \
- src/singletons/Settings.hpp \
- src/singletons/Updates.hpp \
- src/singletons/NativeMessaging.hpp \
- src/singletons/Theme.hpp \
- src/common/SignalVector.hpp \
- src/widgets/dialogs/LogsPopup.hpp \
- src/controllers/moderationactions/ModerationActionModel.hpp \
- src/widgets/settingspages/LookPage.hpp \
- src/widgets/settingspages/FeelPage.hpp \
- src/util/InitUpdateButton.hpp \
- src/widgets/dialogs/UpdateDialog.hpp \
- src/widgets/settingspages/IgnoresPage.hpp \
- src/providers/twitch/PubsubClient.hpp \
- src/providers/twitch/TwitchApi.hpp \
- src/messages/Emote.hpp \
- src/messages/ImageSet.hpp \
- src/providers/bttv/BttvEmotes.hpp \
- src/providers/LinkResolver.hpp \
- src/providers/ffz/FfzEmotes.hpp \
- src/autogenerated/ResourcesAutogen.hpp \
- src/singletons/Badges.hpp \
- src/providers/twitch/TwitchBadges.hpp \
- src/providers/chatterino/ChatterinoBadges.hpp \
- src/common/Aliases.hpp \
- src/providers/twitch/TwitchParseCheerEmotes.hpp \
- src/providers/bttv/LoadBttvChannelEmote.hpp \
- src/util/JsonQuery.hpp \
- src/RunGui.hpp \
- src/BrowserExtension.hpp \
- src/util/FormatTime.hpp \
- src/controllers/notifications/NotificationModel.hpp \
- src/singletons/Toasts.hpp \
- src/common/DownloadManager.hpp \
- src/messages/MessageContainer.hpp \
- src/common/UsernameSet.hpp \
- src/widgets/settingspages/AdvancedPage.hpp \
- src/util/IncognitoBrowser.hpp \
- src/widgets/splits/ClosedSplits.hpp \
- src/providers/ffz/FfzModBadge.hpp \
- src/widgets/settingspages/GeneralPage.hpp \
- src/messages/HistoricMessageAppearance.hpp \
- src/providers/twitch/ChatroomChannel.hpp
-
-RESOURCES += \
- resources/resources.qrc \
- resources/resources_autogenerated.qrc
-
-DISTFILES +=
-
-FORMS +=
-
-# do not use windows min/max macros
-#win32 {
-# DEFINES += NOMINMAX
-#}
+QT += widgets core gui network multimedia svg concurrent
+CONFIG += communi
+COMMUNI += core model util
+
+INCLUDEPATH += src/
+TARGET = chatterino
+TEMPLATE = app
+PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
+CONFIG += precompile_header
+DEFINES += CHATTERINO
+DEFINES += "AB_NAMESPACE=chatterino"
+DEFINES += AB_CUSTOM_THEME
+DEFINES += AB_CUSTOM_SETTINGS
+CONFIG += AB_NOT_STANDALONE
+
+useBreakpad {
+ LIBS += -L$$PWD/lib/qBreakpad/handler/build
+ include(lib/qBreakpad/qBreakpad.pri)
+ DEFINES += C_USE_BREAKPAD
+}
+
+# https://bugreports.qt.io/browse/QTBUG-27018
+equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
+ TARGET = bin/chatterino
+}
+
+# Icons
+macx:ICON = resources/chatterino.icns
+win32:RC_FILE = resources/windows.rc
+
+macx {
+ LIBS += -L/usr/local/lib
+}
+
+# Submodules
+include(lib/appbase.pri)
+include(lib/humanize.pri)
+DEFINES += IRC_NAMESPACE=Communi
+include(lib/libcommuni.pri)
+include(lib/websocketpp.pri)
+include(lib/openssl.pri)
+include(lib/wintoast.pri)
+
+# Optional feature: QtWebEngine
+#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
+# message(Using QWebEngine)
+# QT += webenginewidgets
+# DEFINES += "USEWEBENGINE"
+#}
+
+SOURCES += \
+ src/Application.cpp \
+ src/autogenerated/ResourcesAutogen.cpp \
+ src/BrowserExtension.cpp \
+ src/common/Channel.cpp \
+ src/common/CompletionModel.cpp \
+ src/common/DownloadManager.cpp \
+ src/common/Env.cpp \
+ src/common/LinkParser.cpp \
+ src/common/NetworkData.cpp \
+ src/common/NetworkManager.cpp \
+ src/common/NetworkRequest.cpp \
+ src/common/NetworkResult.cpp \
+ src/common/NetworkTimer.cpp \
+ src/common/UsernameSet.cpp \
+ src/controllers/accounts/Account.cpp \
+ src/controllers/accounts/AccountController.cpp \
+ src/controllers/accounts/AccountModel.cpp \
+ src/controllers/commands/Command.cpp \
+ 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/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/taggedusers/TaggedUser.cpp \
+ src/controllers/taggedusers/TaggedUsersController.cpp \
+ src/controllers/taggedusers/TaggedUsersModel.cpp \
+ src/main.cpp \
+ src/messages/Emote.cpp \
+ src/messages/Image.cpp \
+ src/messages/ImageSet.cpp \
+ src/messages/layouts/MessageLayout.cpp \
+ src/messages/layouts/MessageLayoutContainer.cpp \
+ src/messages/layouts/MessageLayoutElement.cpp \
+ src/messages/Link.cpp \
+ src/messages/Message.cpp \
+ src/messages/MessageBuilder.cpp \
+ src/messages/MessageColor.cpp \
+ src/messages/MessageContainer.cpp \
+ src/messages/MessageElement.cpp \
+ src/providers/bttv/BttvEmotes.cpp \
+ src/providers/bttv/LoadBttvChannelEmote.cpp \
+ src/providers/chatterino/ChatterinoBadges.cpp \
+ src/providers/emoji/Emojis.cpp \
+ src/providers/ffz/FfzEmotes.cpp \
+ src/providers/ffz/FfzModBadge.cpp \
+ src/providers/irc/AbstractIrcServer.cpp \
+ src/providers/irc/IrcAccount.cpp \
+ src/providers/irc/IrcChannel2.cpp \
+ src/providers/irc/IrcConnection2.cpp \
+ src/providers/irc/IrcServer.cpp \
+ src/providers/LinkResolver.cpp \
+ src/providers/twitch/ChatroomChannel.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/TwitchBadges.cpp \
+ src/providers/twitch/TwitchChannel.cpp \
+ src/providers/twitch/TwitchEmotes.cpp \
+ src/providers/twitch/TwitchHelpers.cpp \
+ src/providers/twitch/TwitchMessageBuilder.cpp \
+ src/providers/twitch/TwitchParseCheerEmotes.cpp \
+ src/providers/twitch/TwitchServer.cpp \
+ src/providers/twitch/TwitchUser.cpp \
+ src/RunGui.cpp \
+ src/singletons/Badges.cpp \
+ src/singletons/Emotes.cpp \
+ src/singletons/helper/GifTimer.cpp \
+ src/singletons/helper/LoggingChannel.cpp \
+ src/singletons/Logging.cpp \
+ src/singletons/NativeMessaging.cpp \
+ src/singletons/Paths.cpp \
+ src/singletons/Resources.cpp \
+ src/singletons/Settings.cpp \
+ src/singletons/Theme.cpp \
+ src/singletons/Toasts.cpp \
+ src/singletons/Updates.cpp \
+ src/singletons/WindowManager.cpp \
+ src/singletons/TooltipPreviewImage.cpp \
+ src/util/DebugCount.cpp \
+ src/util/FormatTime.cpp \
+ src/util/IncognitoBrowser.cpp \
+ src/util/InitUpdateButton.cpp \
+ src/util/JsonQuery.cpp \
+ src/util/RapidjsonHelpers.cpp \
+ src/util/StreamLink.cpp \
+ src/widgets/AccountSwitchPopupWidget.cpp \
+ src/widgets/AccountSwitchWidget.cpp \
+ src/widgets/AttachedWindow.cpp \
+ src/widgets/dialogs/EmotePopup.cpp \
+ src/widgets/dialogs/LastRunCrashDialog.cpp \
+ src/widgets/dialogs/LoginDialog.cpp \
+ src/widgets/dialogs/LogsPopup.cpp \
+ src/widgets/dialogs/NotificationPopup.cpp \
+ src/widgets/dialogs/QualityPopup.cpp \
+ src/widgets/dialogs/SelectChannelDialog.cpp \
+ src/widgets/dialogs/SettingsDialog.cpp \
+ src/widgets/dialogs/TextInputDialog.cpp \
+ src/widgets/dialogs/UpdateDialog.cpp \
+ src/widgets/dialogs/UserInfoPopup.cpp \
+ src/widgets/dialogs/WelcomeDialog.cpp \
+ src/widgets/helper/ChannelView.cpp \
+ src/widgets/helper/ComboBoxItemDelegate.cpp \
+ src/widgets/helper/DebugPopup.cpp \
+ src/widgets/helper/EditableModelView.cpp \
+ src/widgets/helper/NotebookButton.cpp \
+ src/widgets/helper/NotebookTab.cpp \
+ src/widgets/helper/ResizingTextEdit.cpp \
+ src/widgets/helper/ScrollbarHighlight.cpp \
+ src/widgets/helper/SearchPopup.cpp \
+ src/widgets/helper/SettingsDialogTab.cpp \
+ src/widgets/Notebook.cpp \
+ src/widgets/Scrollbar.cpp \
+ src/widgets/settingspages/AboutPage.cpp \
+ src/widgets/settingspages/AccountsPage.cpp \
+ src/widgets/settingspages/AdvancedPage.cpp \
+ src/widgets/settingspages/BrowserExtensionPage.cpp \
+ src/widgets/settingspages/CommandPage.cpp \
+ src/widgets/settingspages/EmotesPage.cpp \
+ src/widgets/settingspages/ExternalToolsPage.cpp \
+ src/widgets/settingspages/FeelPage.cpp \
+ src/widgets/settingspages/GeneralPage.cpp \
+ src/widgets/settingspages/HighlightingPage.cpp \
+ src/widgets/settingspages/IgnoresPage.cpp \
+ src/widgets/settingspages/KeyboardSettingsPage.cpp \
+ src/widgets/settingspages/LogsPage.cpp \
+ src/widgets/settingspages/LookPage.cpp \
+ src/widgets/settingspages/ModerationPage.cpp \
+ src/widgets/settingspages/NotificationPage.cpp \
+ src/widgets/settingspages/SettingsPage.cpp \
+ src/widgets/settingspages/SpecialChannelsPage.cpp \
+ src/widgets/splits/ClosedSplits.cpp \
+ src/widgets/splits/Split.cpp \
+ src/widgets/splits/SplitContainer.cpp \
+ src/widgets/splits/SplitHeader.cpp \
+ src/widgets/splits/SplitInput.cpp \
+ src/widgets/splits/SplitOverlay.cpp \
+ src/widgets/StreamView.cpp \
+ src/widgets/Window.cpp \
+ src/controllers/pings/PingController.cpp \
+ src/controllers/pings/PingModel.cpp \
+
+HEADERS += \
+ src/Application.hpp \
+ src/autogenerated/ResourcesAutogen.hpp \
+ src/BrowserExtension.hpp \
+ src/common/Aliases.hpp \
+ src/common/Atomic.hpp \
+ src/common/Channel.hpp \
+ src/common/Common.hpp \
+ src/common/CompletionModel.hpp \
+ src/common/ConcurrentMap.hpp \
+ src/common/DownloadManager.hpp \
+ src/common/LinkParser.hpp \
+ src/common/NetworkCommon.hpp \
+ src/common/NetworkData.hpp \
+ src/common/NetworkManager.hpp \
+ src/common/NetworkRequest.hpp \
+ src/common/NetworkRequester.hpp \
+ src/common/NetworkResult.hpp \
+ src/common/NetworkTimer.hpp \
+ src/common/NetworkWorker.hpp \
+ src/common/NullablePtr.hpp \
+ src/common/ProviderId.hpp \
+ src/common/SignalVector.hpp \
+ src/common/SignalVectorModel.hpp \
+ src/common/UniqueAccess.hpp \
+ src/common/UsernameSet.hpp \
+ src/common/Version.hpp \
+ src/controllers/accounts/Account.hpp \
+ src/controllers/accounts/AccountController.hpp \
+ src/controllers/accounts/AccountModel.hpp \
+ src/controllers/commands/Command.hpp \
+ src/controllers/commands/CommandController.hpp \
+ 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 \
+ src/controllers/ignores/IgnoreController.hpp \
+ src/controllers/ignores/IgnoreModel.hpp \
+ 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/taggedusers/TaggedUser.hpp \
+ src/controllers/taggedusers/TaggedUsersController.hpp \
+ src/controllers/taggedusers/TaggedUsersModel.hpp \
+ src/messages/Emote.hpp \
+ src/messages/Image.hpp \
+ src/messages/ImageSet.hpp \
+ src/messages/layouts/MessageLayout.hpp \
+ src/messages/layouts/MessageLayoutContainer.hpp \
+ src/messages/layouts/MessageLayoutElement.hpp \
+ src/messages/LimitedQueue.hpp \
+ src/messages/LimitedQueueSnapshot.hpp \
+ src/messages/Link.hpp \
+ src/messages/Message.hpp \
+ src/messages/MessageBuilder.hpp \
+ src/messages/MessageColor.hpp \
+ src/messages/MessageContainer.hpp \
+ src/messages/MessageElement.hpp \
+ src/messages/MessageParseArgs.hpp \
+ src/messages/Selection.hpp \
+ src/PrecompiledHeader.hpp \
+ src/providers/bttv/BttvEmotes.hpp \
+ src/providers/bttv/LoadBttvChannelEmote.hpp \
+ src/providers/chatterino/ChatterinoBadges.hpp \
+ src/providers/emoji/Emojis.hpp \
+ src/providers/ffz/FfzEmotes.hpp \
+ src/providers/ffz/FfzModBadge.hpp \
+ src/providers/irc/AbstractIrcServer.hpp \
+ src/providers/irc/IrcAccount.hpp \
+ src/providers/irc/IrcChannel2.hpp \
+ src/providers/irc/IrcConnection2.hpp \
+ src/providers/irc/IrcServer.hpp \
+ src/providers/LinkResolver.hpp \
+ src/providers/twitch/ChatroomChannel.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/TwitchBadges.hpp \
+ src/providers/twitch/TwitchChannel.hpp \
+ src/providers/twitch/TwitchCommon.hpp \
+ src/providers/twitch/TwitchEmotes.hpp \
+ src/providers/twitch/TwitchHelpers.hpp \
+ src/providers/twitch/TwitchMessageBuilder.hpp \
+ src/providers/twitch/TwitchParseCheerEmotes.hpp \
+ src/providers/twitch/TwitchServer.hpp \
+ src/providers/twitch/TwitchUser.hpp \
+ src/RunGui.hpp \
+ src/singletons/TooltipPreviewImage.hpp \
+ src/singletons/Badges.hpp \
+ src/singletons/Emotes.hpp \
+ src/singletons/helper/GifTimer.hpp \
+ src/singletons/helper/LoggingChannel.hpp \
+ src/singletons/Logging.hpp \
+ src/singletons/NativeMessaging.hpp \
+ src/singletons/Paths.hpp \
+ src/singletons/Resources.hpp \
+ src/singletons/Settings.hpp \
+ src/singletons/Theme.hpp \
+ src/singletons/Toasts.hpp \
+ src/singletons/Updates.hpp \
+ src/singletons/WindowManager.hpp \
+ src/util/ConcurrentMap.hpp \
+ src/util/DebugCount.hpp \
+ src/util/FormatTime.hpp \
+ src/util/IncognitoBrowser.hpp \
+ src/util/InitUpdateButton.hpp \
+ src/util/IrcHelpers.hpp \
+ src/util/IsBigEndian.hpp \
+ src/util/JsonQuery.hpp \
+ src/util/LayoutCreator.hpp \
+ src/util/QStringHash.hpp \
+ src/util/rangealgorithm.hpp \
+ src/util/RapidjsonHelpers.hpp \
+ src/util/RemoveScrollAreaBackground.hpp \
+ src/util/SharedPtrElementLess.hpp \
+ src/util/StandardItemHelper.hpp \
+ src/util/StreamLink.hpp \
+ src/widgets/AccountSwitchPopupWidget.hpp \
+ src/widgets/AccountSwitchWidget.hpp \
+ src/widgets/AttachedWindow.hpp \
+ src/widgets/dialogs/EmotePopup.hpp \
+ src/widgets/dialogs/LastRunCrashDialog.hpp \
+ src/widgets/dialogs/LoginDialog.hpp \
+ src/widgets/dialogs/LogsPopup.hpp \
+ src/widgets/dialogs/NotificationPopup.hpp \
+ src/widgets/dialogs/QualityPopup.hpp \
+ src/widgets/dialogs/SelectChannelDialog.hpp \
+ src/widgets/dialogs/SettingsDialog.hpp \
+ src/widgets/dialogs/TextInputDialog.hpp \
+ src/widgets/dialogs/UpdateDialog.hpp \
+ src/widgets/dialogs/UserInfoPopup.hpp \
+ src/widgets/dialogs/WelcomeDialog.hpp \
+ src/widgets/helper/ChannelView.hpp \
+ src/widgets/helper/ComboBoxItemDelegate.hpp \
+ src/widgets/helper/CommonTexts.hpp \
+ src/widgets/helper/DebugPopup.hpp \
+ src/widgets/helper/EditableModelView.hpp \
+ src/widgets/helper/Line.hpp \
+ src/widgets/helper/NotebookButton.hpp \
+ src/widgets/helper/NotebookTab.hpp \
+ src/widgets/helper/ResizingTextEdit.hpp \
+ src/widgets/helper/ScrollbarHighlight.hpp \
+ src/widgets/helper/SearchPopup.hpp \
+ src/widgets/helper/SettingsDialogTab.hpp \
+ src/widgets/Notebook.hpp \
+ src/widgets/Scrollbar.hpp \
+ src/widgets/settingspages/AboutPage.hpp \
+ src/widgets/settingspages/AccountsPage.hpp \
+ src/widgets/settingspages/AdvancedPage.hpp \
+ src/widgets/settingspages/BrowserExtensionPage.hpp \
+ src/widgets/settingspages/CommandPage.hpp \
+ src/widgets/settingspages/EmotesPage.hpp \
+ src/widgets/settingspages/ExternalToolsPage.hpp \
+ src/widgets/settingspages/FeelPage.hpp \
+ src/widgets/settingspages/GeneralPage.hpp \
+ src/widgets/settingspages/HighlightingPage.hpp \
+ src/widgets/settingspages/IgnoresPage.hpp \
+ src/widgets/settingspages/KeyboardSettingsPage.hpp \
+ src/widgets/settingspages/LogsPage.hpp \
+ src/widgets/settingspages/LookPage.hpp \
+ src/widgets/settingspages/ModerationPage.hpp \
+ src/widgets/settingspages/NotificationPage.hpp \
+ src/widgets/settingspages/SettingsPage.hpp \
+ src/widgets/settingspages/SpecialChannelsPage.hpp \
+ src/widgets/splits/ClosedSplits.hpp \
+ src/widgets/splits/Split.hpp \
+ src/widgets/splits/SplitContainer.hpp \
+ src/widgets/splits/SplitHeader.hpp \
+ src/widgets/splits/SplitInput.hpp \
+ src/widgets/splits/SplitOverlay.hpp \
+ src/widgets/StreamView.hpp \
+ src/widgets/Window.hpp \
+ src/controllers/pings/PingController.hpp \
+ src/controllers/pings/PingModel.hpp \
+
+RESOURCES += \
+ resources/resources.qrc \
+ resources/resources_autogenerated.qrc
+
+DISTFILES +=
+
+FORMS +=
+
+# do not use windows min/max macros
+#win32 {
+# DEFINES += NOMINMAX
+#}
+
+linux:isEmpty(PREFIX) {
+ message("Using default installation prefix (/usr/local). Change PREFIX in qmake command")
+ PREFIX = /usr/local
+}
+
+linux {
+ desktop.files = resources/chatterino.desktop
+ desktop.path = $$PREFIX/share/applications
+
+ build_icons.path = .
+ build_icons.commands = @echo $$PWD && mkdir -p $$PWD/resources/linuxinstall/icons/hicolor/256x256 && cp $$PWD/resources/icon.png $$PWD/resources/linuxinstall/icons/hicolor/256x256/chatterino.png
+
+ icon.files = $$PWD/resources/linuxinstall/icons/hicolor/256x256/chatterino.png
+ icon.path = $$PREFIX/share/icons/hicolor/256x256/apps
+
+ target.path = $$PREFIX/bin
+
+ INSTALLS += desktop build_icons icon target
+}
diff --git a/docs/ENV.md b/docs/ENV.md
new file mode 100644
index 000000000..586aac354
--- /dev/null
+++ b/docs/ENV.md
@@ -0,0 +1,20 @@
+# Environment variables
+Below I have tried to list all environment variables that can be used to modify the behaviour of Chatterino. Used for things that I don't feel like fit in the settings system.
+
+### CHATTERINO2_RECENT_MESSAGES_URL
+Used to change the URL that Chatterino2 uses when trying to load historic Twitch chat messages (if the setting is enabled).
+Default value: `https://recent-messages.robotty.de/api/v2/recent-messages/%1?clearchatToNotice=true`
+Arguments:
+ - `%1` = Name of the Twitch channel
+
+### CHATTERINO2_LINK_RESOLVER_URL
+Used to change the URL that Chatterino2 uses when trying to get link information to display in the tooltip on hover.
+Default value: `https://braize.pajlada.com/chatterino/link_resolver/%1`
+Arguments:
+ - `%1` = Escaped URL the link resolver should resolve
+
+### CHATTERINO2_TWITCH_EMOTE_SET_RESOLVER_URL
+Used to change the URL that Chatterino2 uses when trying to get emote set information
+Default value: `https://braize.pajlada.com/chatterino/twitchemotes/set/%1/`
+Arguments:
+ - `%1` = Emote set ID
diff --git a/lib/appbase b/lib/appbase
index e6a31d522..d05492573 160000
--- a/lib/appbase
+++ b/lib/appbase
@@ -1 +1 @@
-Subproject commit e6a31d5228ed8969596d6fdbd030f71a7a17f30d
+Subproject commit d054925734cf26576346a1da856f7ab0d4b6c0a5
diff --git a/resources/.gitignore b/resources/.gitignore
new file mode 100644
index 000000000..1d38bf357
--- /dev/null
+++ b/resources/.gitignore
@@ -0,0 +1 @@
+linuxinstall
diff --git a/resources/buttons/trashCan.png b/resources/buttons/trashCan.png
new file mode 100644
index 000000000..fd1bce3a0
Binary files /dev/null and b/resources/buttons/trashCan.png differ
diff --git a/resources/buttons/trashcan.svg b/resources/buttons/trashcan.svg
new file mode 100644
index 000000000..e225ee4ea
--- /dev/null
+++ b/resources/buttons/trashcan.svg
@@ -0,0 +1,124 @@
+
+
+
+
diff --git a/resources/chatterino.desktop b/resources/chatterino.desktop
new file mode 100644
index 000000000..23cc8c356
--- /dev/null
+++ b/resources/chatterino.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Version=1.0
+Name=Chatterino
+Comment=Chat client for Twitch
+Exec=chatterino
+Icon=chatterino
+Terminal=false
+Categories=Network;InstantMessaging;
diff --git a/resources/chatterino2.icns b/resources/chatterino.icns
similarity index 100%
rename from resources/chatterino2.icns
rename to resources/chatterino.icns
diff --git a/resources/generate_resources.py b/resources/generate_resources.py
index 5239e64e8..e48795f72 100755
--- a/resources/generate_resources.py
+++ b/resources/generate_resources.py
@@ -8,7 +8,7 @@ ignored_files = ['qt.conf', 'resources.qrc', 'resources_autogenerated.qrc', 'win
# to ignore all files in a/b, add a/b to ignored_directories.
# this will ignore a/b/c/d.txt and a/b/xd.txt
-ignored_directories = ['__pycache__']
+ignored_directories = ['__pycache__', 'linuxinstall']
def isNotIgnored(file):
# check if file exists in an ignored direcory
diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc
index fcf0b5dac..96204e348 100644
--- a/resources/resources_autogenerated.qrc
+++ b/resources/resources_autogenerated.qrc
@@ -1,5 +1,5 @@
- chatterino2.icns
+ chatterino.icns
contributors.txt
emoji.json
emojidata.txt
@@ -24,6 +24,7 @@
buttons/modModeEnabled.png
buttons/modModeEnabled2.png
buttons/timeout.png
+ buttons/trashCan.png
buttons/unban.png
buttons/unmod.png
buttons/update.png
diff --git a/src/Application.cpp b/src/Application.cpp
index 2870c6ed6..42067d9bd 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -6,6 +6,7 @@
#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 "debug/Log.hpp"
#include "messages/MessageBuilder.hpp"
@@ -53,6 +54,7 @@ Application::Application(Settings &_settings, Paths &_paths)
, commands(&this->emplace())
, highlights(&this->emplace())
, notifications(&this->emplace())
+ , pings(&this->emplace())
, ignores(&this->emplace())
, taggedUsers(&this->emplace())
, moderationActions(&this->emplace())
@@ -264,6 +266,7 @@ void Application::initPubsub()
auto msg = MessageBuilder(action).release();
postToThread([chan, msg] { chan->addMessage(msg); });
+ chan->deleteMessage(msg->id);
});
this->twitch.pubsub->start();
diff --git a/src/Application.hpp b/src/Application.hpp
index c872a4c62..49c8fe96c 100644
--- a/src/Application.hpp
+++ b/src/Application.hpp
@@ -18,6 +18,7 @@ class TaggedUsersController;
class AccountController;
class ModerationActions;
class NotificationController;
+class PingController;
class Theme;
class WindowManager;
@@ -62,6 +63,7 @@ public:
CommandController *const commands{};
HighlightController *const highlights{};
NotificationController *const notifications{};
+ PingController *const pings{};
IgnoreController *const ignores{};
TaggedUsersController *const taggedUsers{};
ModerationActions *const moderationActions{};
diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp
index 34cf039ef..59f5fce70 100644
--- a/src/autogenerated/ResourcesAutogen.cpp
+++ b/src/autogenerated/ResourcesAutogen.cpp
@@ -18,6 +18,7 @@ Resources2::Resources2()
this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
this->buttons.timeout = QPixmap(":/buttons/timeout.png");
+ this->buttons.trashCan = QPixmap(":/buttons/trashCan.png");
this->buttons.unban = QPixmap(":/buttons/unban.png");
this->buttons.unmod = QPixmap(":/buttons/unmod.png");
this->buttons.update = QPixmap(":/buttons/update.png");
diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp
index fbbb2fbf3..1c8a0bce4 100644
--- a/src/autogenerated/ResourcesAutogen.hpp
+++ b/src/autogenerated/ResourcesAutogen.hpp
@@ -24,6 +24,7 @@ public:
QPixmap modModeEnabled;
QPixmap modModeEnabled2;
QPixmap timeout;
+ QPixmap trashCan;
QPixmap unban;
QPixmap unmod;
QPixmap update;
diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp
index 8d247cf2f..caa432752 100644
--- a/src/common/Channel.cpp
+++ b/src/common/Channel.cpp
@@ -78,7 +78,7 @@ void Channel::addMessage(MessagePtr message,
// FOURTF: change this when adding more providers
if (this->isTwitchChannel() &&
- (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
+ (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
{
app->logging->addMessage(this->name_, message);
}
@@ -154,8 +154,9 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = 0; i < snapshotLength; i++)
{
auto &s = snapshot[i];
- if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) &&
- s->loginName == message->timeoutUser)
+ if (s->loginName == message->timeoutUser &&
+ s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout,
+ MessageFlag::Whisper}))
{
// FOURTF: disabled for now
// PAJLADA: Shitty solution described in Message.hpp
@@ -179,7 +180,8 @@ void Channel::disableAllMessages()
for (int i = 0; i < snapshotLength; i++)
{
auto &message = snapshot[i];
- if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout}))
+ if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout,
+ MessageFlag::Whisper}))
{
continue;
}
@@ -210,6 +212,25 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
}
}
+void Channel::deleteMessage(QString messageID)
+{
+ LimitedQueueSnapshot snapshot = this->getMessageSnapshot();
+ int snapshotLength = snapshot.size();
+
+ int end = std::max(0, snapshotLength - 200);
+
+ for (int i = snapshotLength - 1; i >= end; --i)
+ {
+ auto &s = snapshot[i];
+
+ if (s->id == messageID)
+ {
+ s->flags.set(MessageFlag::Disabled);
+ break;
+ }
+ }
+}
+
void Channel::addRecentChatter(const MessagePtr &message)
{
}
@@ -239,6 +260,11 @@ bool Channel::hasModRights() const
return this->isMod() || this->isBroadcaster();
}
+bool Channel::hasHighRateLimit() const
+{
+ return this->isMod() || this->isBroadcaster();
+}
+
bool Channel::isLive() const
{
return false;
diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp
index 9e111aa46..9abc668ea 100644
--- a/src/common/Channel.hpp
+++ b/src/common/Channel.hpp
@@ -15,7 +15,7 @@ namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr;
-enum class MessageFlag : uint16_t;
+enum class MessageFlag : uint32_t;
using MessageFlags = FlagsEnum;
class Channel : public std::enable_shared_from_this
@@ -62,6 +62,7 @@ public:
void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement);
+ void deleteMessage(QString messageID);
QStringList modList;
@@ -70,6 +71,7 @@ public:
virtual bool isMod() const;
virtual bool isBroadcaster() const;
virtual bool hasModRights() const;
+ virtual bool hasHighRateLimit() const;
virtual bool isLive() const;
virtual bool shouldIgnoreHighlights() const;
diff --git a/src/common/DownloadManager.cpp b/src/common/DownloadManager.cpp
index 286150a7c..5adc8ef32 100644
--- a/src/common/DownloadManager.cpp
+++ b/src/common/DownloadManager.cpp
@@ -20,9 +20,7 @@ DownloadManager::~DownloadManager()
void DownloadManager::setFile(QString fileURL, const QString &channelName)
{
- QString filePath = fileURL;
QString saveFilePath;
- QStringList filePathList = filePath.split('/');
saveFilePath =
getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png";
QNetworkRequest request;
diff --git a/src/common/Env.cpp b/src/common/Env.cpp
new file mode 100644
index 000000000..ff711ec47
--- /dev/null
+++ b/src/common/Env.cpp
@@ -0,0 +1,40 @@
+#include "common/Env.hpp"
+
+namespace chatterino {
+
+namespace {
+
+ QString readStringEnv(const char *envName, QString defaultValue)
+ {
+ auto envString = std::getenv(envName);
+ if (envString != nullptr)
+ {
+ return QString(envString);
+ }
+
+ return defaultValue;
+ }
+
+} // namespace
+
+Env::Env()
+ : recentMessagesApiUrl(
+ readStringEnv("CHATTERINO2_RECENT_MESSAGES_URL",
+ "https://recent-messages.robotty.de/api/v2/"
+ "recent-messages/%1?clearchatToNotice=true"))
+ , linkResolverUrl(readStringEnv(
+ "CHATTERINO2_LINK_RESOLVER_URL",
+ "https://braize.pajlada.com/chatterino/link_resolver/%1"))
+ , twitchEmoteSetResolverUrl(readStringEnv(
+ "CHATTERINO2_TWITCH_EMOTE_SET_RESOLVER_URL",
+ "https://braize.pajlada.com/chatterino/twitchemotes/set/%1/"))
+{
+}
+
+const Env &Env::get()
+{
+ static Env instance;
+ return instance;
+}
+
+} // namespace chatterino
diff --git a/src/common/Env.hpp b/src/common/Env.hpp
new file mode 100644
index 000000000..2dc7fa1ba
--- /dev/null
+++ b/src/common/Env.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include
+
+namespace chatterino {
+
+class Env
+{
+ Env();
+
+public:
+ static const Env &get();
+
+ const QString recentMessagesApiUrl;
+ const QString linkResolverUrl;
+ const QString twitchEmoteSetResolverUrl;
+};
+
+} // namespace chatterino
diff --git a/src/common/SignalVectorModel.hpp b/src/common/SignalVectorModel.hpp
index 187356dea..d79263830 100644
--- a/src/common/SignalVectorModel.hpp
+++ b/src/common/SignalVectorModel.hpp
@@ -188,7 +188,7 @@ public:
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
column < this->columnCount_);
- return this->rows_[index.row()].items[index.column()]->flags();
+ return this->rows_[row].items[column]->flags();
}
QStandardItem *getItem(int row, int column)
diff --git a/src/common/UsernameSet.cpp b/src/common/UsernameSet.cpp
index 8e0a53af7..964b6c0a7 100644
--- a/src/common/UsernameSet.cpp
+++ b/src/common/UsernameSet.cpp
@@ -59,7 +59,7 @@ void UsernameSet::insertPrefix(const QString &value)
{
auto &string = this->firstKeyForPrefix[Prefix(value)];
- if (string.isNull() || value < string)
+ if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
string = value;
}
@@ -99,6 +99,11 @@ bool Prefix::operator==(const Prefix &other) const
std::tie(other.first, other.second);
}
+bool Prefix::operator!=(const Prefix &other) const
+{
+ return !(*this == other);
+}
+
bool Prefix::isStartOf(const QString &string) const
{
if (string.size() == 0)
diff --git a/src/common/UsernameSet.hpp b/src/common/UsernameSet.hpp
index d71bcdaf6..f07911513 100644
--- a/src/common/UsernameSet.hpp
+++ b/src/common/UsernameSet.hpp
@@ -6,11 +6,13 @@
#include
namespace chatterino {
+
class Prefix
{
public:
Prefix(const QString &string);
bool operator==(const Prefix &other) const;
+ bool operator!=(const Prefix &other) const;
bool isStartOf(const QString &string) const;
private:
@@ -19,9 +21,11 @@ private:
friend struct std::hash;
};
+
} // namespace chatterino
namespace std {
+
template <>
struct hash {
size_t operator()(const chatterino::Prefix &prefix) const
@@ -30,9 +34,18 @@ struct hash {
size_t(prefix.second.unicode());
}
};
+
} // namespace std
namespace chatterino {
+
+struct CaseInsensitiveLess {
+ bool operator()(const QString &lhs, const QString &rhs) const
+ {
+ return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
+ }
+};
+
class UsernameSet
{
public:
@@ -66,7 +79,7 @@ public:
private:
void insertPrefix(const QString &string);
- std::set items;
+ std::set items;
std::unordered_map firstKeyForPrefix;
};
diff --git a/src/controllers/accounts/AccountModel.cpp b/src/controllers/accounts/AccountModel.cpp
index 6e1d2a5df..875efbdc5 100644
--- a/src/controllers/accounts/AccountModel.cpp
+++ b/src/controllers/accounts/AccountModel.cpp
@@ -31,12 +31,12 @@ int AccountModel::beforeInsert(const std::shared_ptr &item,
{
if (this->categoryCount_[item->getCategory()]++ == 0)
{
- auto row = this->createRow();
+ auto newRow = this->createRow();
- setStringItem(row[0], item->getCategory(), false, false);
- row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
+ setStringItem(newRow[0], item->getCategory(), false, false);
+ newRow[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
- this->insertCustomRow(std::move(row), proposedIndex);
+ this->insertCustomRow(std::move(newRow), proposedIndex);
return proposedIndex + 1;
}
diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp
index 8d98a999a..ae46f6e2e 100644
--- a/src/controllers/commands/CommandController.cpp
+++ b/src/controllers/commands/CommandController.cpp
@@ -15,6 +15,7 @@
#include "singletons/Emotes.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
+#include "singletons/Theme.hpp"
#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp"
@@ -28,9 +29,143 @@
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
"/clear", "/subscribers", "/subscribersoff", "/followers", \
- "/followersoff" \
+ "/followersoff", "/user" \
}
+namespace {
+using namespace chatterino;
+
+static const QStringList whisperCommands{"/w", ".w"};
+
+void sendWhisperMessage(const QString &text)
+{
+ // (hemirt) pajlada: "we should not be sending whispers through jtv, but
+ // rather to your own username"
+ auto app = getApp();
+ app->twitch.server->sendMessage("jtv", text.simplified());
+}
+
+bool appendWhisperMessageWordsLocally(const QStringList &words)
+{
+ auto app = getApp();
+
+ MessageBuilder b;
+
+ b.emplace();
+ b.emplace(app->accounts->twitch.getCurrent()->getUserName(),
+ MessageElementFlag::Text, MessageColor::Text,
+ FontStyle::ChatMediumBold);
+ b.emplace("->", MessageElementFlag::Text,
+ getApp()->themes->messages.textColors.system);
+ b.emplace(words[1] + ":", MessageElementFlag::Text,
+ MessageColor::Text, FontStyle::ChatMediumBold);
+
+ const auto &acc = app->accounts->twitch.getCurrent();
+ const auto &accemotes = *acc->accessEmotes();
+ const auto &bttvemotes = app->twitch.server->getBttvEmotes();
+ const auto &ffzemotes = app->twitch.server->getFfzEmotes();
+ auto flags = MessageElementFlags();
+ auto emote = boost::optional{};
+ for (int i = 2; i < words.length(); i++)
+ {
+ { // twitch emote
+ auto it = accemotes.emotes.find({words[i]});
+ if (it != accemotes.emotes.end())
+ {
+ b.emplace(it->second,
+ MessageElementFlag::TwitchEmote);
+ continue;
+ }
+ } // twitch emote
+
+ { // bttv/ffz emote
+ if ((emote = bttvemotes.emote({words[i]})))
+ {
+ flags = MessageElementFlag::BttvEmote;
+ }
+ else if ((emote = ffzemotes.emote({words[i]})))
+ {
+ flags = MessageElementFlag::FfzEmote;
+ }
+ if (emote)
+ {
+ b.emplace(emote.get(), flags);
+ continue;
+ }
+ } // bttv/ffz emote
+ { // emoji/text
+ for (auto &variant : app->emotes->emojis.parse(words[i]))
+ {
+ constexpr const static struct {
+ void operator()(EmotePtr emote, MessageBuilder &b) const
+ {
+ b.emplace(emote,
+ MessageElementFlag::EmojiAll);
+ }
+ void operator()(const QString &string,
+ MessageBuilder &b) const
+ {
+ auto linkString = b.matchLink(string);
+ if (linkString.isEmpty())
+ {
+ b.emplace(string,
+ MessageElementFlag::Text);
+ }
+ else
+ {
+ b.addLink(string, linkString);
+ }
+ }
+ } visitor;
+ boost::apply_visitor([&b](auto &&arg) { visitor(arg, b); },
+ variant);
+ } // emoji/text
+ }
+ }
+
+ b->flags.set(MessageFlag::DoNotTriggerNotification);
+ b->flags.set(MessageFlag::Whisper);
+ auto messagexD = b.release();
+
+ app->twitch.server->whispersChannel->addMessage(messagexD);
+
+ auto overrideFlags = boost::optional(messagexD->flags);
+ overrideFlags->set(MessageFlag::DoNotLog);
+
+ if (getSettings()->inlineWhispers)
+ {
+ app->twitch.server->forEachChannel(
+ [&messagexD, overrideFlags](ChannelPtr _channel) {
+ _channel->addMessage(messagexD, overrideFlags);
+ });
+ }
+
+ return true;
+}
+
+bool appendWhisperMessageStringLocally(const QString &textNoEmoji)
+{
+ QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
+ QStringList words = text.split(' ', QString::SkipEmptyParts);
+
+ if (words.length() == 0)
+ {
+ return false;
+ }
+
+ QString commandName = words[0];
+
+ if (whisperCommands.contains(commandName, Qt::CaseInsensitive))
+ {
+ if (words.length() > 2)
+ {
+ return appendWhisperMessageWordsLocally(words);
+ }
+ }
+ return false;
+}
+} // namespace
+
namespace chatterino {
void CommandController::initialize(Settings &, Paths &paths)
@@ -122,99 +257,12 @@ QString CommandController::execCommand(const QString &textNoEmoji,
// works in a valid twitch channel and /whispers, etc...
if (!dryRun && channel->isTwitchChannel())
{
- if (commandName == "/w")
+ if (whisperCommands.contains(commandName, Qt::CaseInsensitive))
{
- if (words.length() <= 2)
+ if (words.length() > 2)
{
- return "";
- }
-
- auto app = getApp();
-
- MessageBuilder b;
-
- b.emplace();
- b.emplace(
- app->accounts->twitch.getCurrent()->getUserName(),
- MessageElementFlag::Text, MessageColor::Text,
- FontStyle::ChatMediumBold);
- b.emplace("->", MessageElementFlag::Text);
- b.emplace(words[1] + ":", MessageElementFlag::Text,
- MessageColor::Text,
- FontStyle::ChatMediumBold);
-
- const auto &acc = app->accounts->twitch.getCurrent();
- const auto &accemotes = *acc->accessEmotes();
- const auto &bttvemotes = app->twitch.server->getBttvEmotes();
- const auto &ffzemotes = app->twitch.server->getFfzEmotes();
- auto flags = MessageElementFlags();
- auto emote = boost::optional{};
- for (int i = 2; i < words.length(); i++)
- {
- { // twitch emote
- auto it = accemotes.emotes.find({words[i]});
- if (it != accemotes.emotes.end())
- {
- b.emplace(
- it->second, MessageElementFlag::TwitchEmote);
- continue;
- }
- } // twitch emote
-
- { // bttv/ffz emote
- if ((emote = bttvemotes.emote({words[i]})))
- {
- flags = MessageElementFlag::BttvEmote;
- }
- else if ((emote = ffzemotes.emote({words[i]})))
- {
- flags = MessageElementFlag::FfzEmote;
- }
- if (emote)
- {
- b.emplace(emote.get(), flags);
- continue;
- }
- } // bttv/ffz emote
- { // emoji/text
- for (auto &variant : app->emotes->emojis.parse(words[i]))
- {
- constexpr const static struct {
- void operator()(EmotePtr emote,
- MessageBuilder &b) const
- {
- b.emplace(
- emote, MessageElementFlag::EmojiAll);
- }
- void operator()(const QString &string,
- MessageBuilder &b) const
- {
- b.emplace(
- string, MessageElementFlag::Text);
- }
- } visitor;
- boost::apply_visitor(
- [&b](auto &&arg) { visitor(arg, b); }, variant);
- } // emoji/text
- }
- }
-
- b->flags.set(MessageFlag::DoNotTriggerNotification);
- auto messagexD = b.release();
-
- app->twitch.server->whispersChannel->addMessage(messagexD);
-
- app->twitch.server->sendMessage("jtv", text);
-
- auto overrideFlags = boost::optional(messagexD->flags);
- overrideFlags->set(MessageFlag::DoNotLog);
-
- if (getSettings()->inlineWhispers)
- {
- app->twitch.server->forEachChannel(
- [&messagexD, overrideFlags](ChannelPtr _channel) {
- _channel->addMessage(messagexD, overrideFlags);
- });
+ appendWhisperMessageWordsLocally(words);
+ sendWhisperMessage(text);
}
return "";
@@ -413,13 +461,36 @@ QString CommandController::execCommand(const QString &textNoEmoji,
logs->show();
return "";
}
+ else if (commandName == "/user")
+ {
+ if (words.size() < 2)
+ {
+ channel->addMessage(
+ makeSystemMessage("Usage /user [user] (channel)"));
+ return "";
+ }
+ QString channelName = channel->getName();
+ if (words.size() > 2)
+ {
+ channelName = words[2];
+ if (channelName[0] == '#')
+ {
+ channelName.remove(0, 1);
+ }
+ }
+ QDesktopServices::openUrl("https://www.twitch.tv/popout/" +
+ channelName + "/viewercard/" + words[1]);
+ return "";
+ }
}
- // check if custom command exists
- auto it = this->commandsMap_.find(commandName);
- if (it != this->commandsMap_.end())
{
- return this->execCustomCommand(words, it.value());
+ // check if custom command exists
+ const auto it = this->commandsMap_.find(commandName);
+ if (it != this->commandsMap_.end())
+ {
+ return this->execCustomCommand(words, it.value(), dryRun);
+ }
}
auto maxSpaces = std::min(this->maxSpaces_, words.length() - 1);
@@ -427,10 +498,10 @@ QString CommandController::execCommand(const QString &textNoEmoji,
{
commandName += ' ' + words[i + 1];
- auto it = this->commandsMap_.find(commandName);
+ const auto it = this->commandsMap_.find(commandName);
if (it != this->commandsMap_.end())
{
- return this->execCustomCommand(words, it.value());
+ return this->execCustomCommand(words, it.value(), dryRun);
}
}
@@ -438,7 +509,8 @@ QString CommandController::execCommand(const QString &textNoEmoji,
}
QString CommandController::execCustomCommand(const QStringList &words,
- const Command &command)
+ const Command &command,
+ bool dryRun)
{
QString result;
@@ -509,7 +581,17 @@ QString CommandController::execCustomCommand(const QStringList &words,
result = result.mid(1);
}
- return result.replace("{{", "{");
+ auto res = result.replace("{{", "{");
+
+ if (dryRun || !appendWhisperMessageStringLocally(res))
+ {
+ return res;
+ }
+ else
+ {
+ sendWhisperMessage(res);
+ return "";
+ }
}
QStringList CommandController::getDefaultTwitchCommandList()
diff --git a/src/controllers/commands/CommandController.hpp b/src/controllers/commands/CommandController.hpp
index fb6f9d89d..e29ee25a9 100644
--- a/src/controllers/commands/CommandController.hpp
+++ b/src/controllers/commands/CommandController.hpp
@@ -49,7 +49,8 @@ private:
std::unique_ptr>>
commandsSetting_;
- QString execCustomCommand(const QStringList &words, const Command &command);
+ QString execCustomCommand(const QStringList &words, const Command &command,
+ bool dryRun);
};
} // namespace chatterino
diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp
index 10c83ac10..1279f4a88 100644
--- a/src/controllers/ignores/IgnoreController.hpp
+++ b/src/controllers/ignores/IgnoreController.hpp
@@ -12,6 +12,8 @@ class Paths;
class IgnoreModel;
+enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
+
class IgnoreController final : public Singleton
{
public:
diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp
index 0b6418a57..199ea5521 100644
--- a/src/controllers/moderationactions/ModerationAction.cpp
+++ b/src/controllers/moderationactions/ModerationAction.cpp
@@ -73,6 +73,10 @@ ModerationAction::ModerationAction(const QString &action)
{
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
}
+ else if (action.startsWith("/delete"))
+ {
+ this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
+ }
else
{
QString xD = action;
diff --git a/src/controllers/notifications/NotificationController.cpp b/src/controllers/notifications/NotificationController.cpp
index 395de1974..081084fba 100644
--- a/src/controllers/notifications/NotificationController.cpp
+++ b/src/controllers/notifications/NotificationController.cpp
@@ -103,16 +103,12 @@ void NotificationController::playSound()
static auto player = new QMediaPlayer;
static QUrl currentPlayerUrl;
- QUrl highlightSoundUrl;
- if (getSettings()->notificationCustomSound)
- {
- highlightSoundUrl = QUrl::fromLocalFile(
- getSettings()->notificationPathSound.getValue());
- }
- else
- {
- highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
- }
+ QUrl highlightSoundUrl =
+ getSettings()->notificationCustomSound
+ ? QUrl::fromLocalFile(
+ getSettings()->notificationPathSound.getValue())
+ : QUrl("qrc:/sounds/ping2.wav");
+
if (currentPlayerUrl != highlightSoundUrl)
{
player->setMedia(highlightSoundUrl);
diff --git a/src/controllers/pings/PingController.cpp b/src/controllers/pings/PingController.cpp
new file mode 100644
index 000000000..0f0ed37d8
--- /dev/null
+++ b/src/controllers/pings/PingController.cpp
@@ -0,0 +1,70 @@
+#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.getVector())
+ {
+ 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
new file mode 100644
index 000000000..80805e856
--- /dev/null
+++ b/src/controllers/pings/PingController.hpp
@@ -0,0 +1,36 @@
+#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
new file mode 100644
index 000000000..28098a209
--- /dev/null
+++ b/src/controllers/pings/PingModel.cpp
@@ -0,0 +1,28 @@
+#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/pings/PingModel.hpp b/src/controllers/pings/PingModel.hpp
new file mode 100644
index 000000000..137be5e0c
--- /dev/null
+++ b/src/controllers/pings/PingModel.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include
+
+#include "common/SignalVectorModel.hpp"
+#include "controllers/notifications/NotificationController.hpp"
+
+namespace chatterino {
+
+class PingController;
+
+class PingModel : public SignalVectorModel
+{
+ explicit PingModel(QObject *parent);
+
+protected:
+ // turn a vector item into a model row
+ virtual QString getItemFromRow(std::vector &row,
+ const QString &original) override;
+
+ // 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/messages/HistoricMessageAppearance.hpp b/src/messages/HistoricMessageAppearance.hpp
deleted file mode 100644
index 54cdb0686..000000000
--- a/src/messages/HistoricMessageAppearance.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#pragma once
-
-namespace chatterino {
-
-enum HistoricMessageAppearance {
- Crossed = (1 << 0),
- Greyed = (1 << 1),
-};
-
-} // namespace chatterino
diff --git a/src/messages/LimitedQueue.hpp b/src/messages/LimitedQueue.hpp
index 3142d8eca..61e8db56f 100644
--- a/src/messages/LimitedQueue.hpp
+++ b/src/messages/LimitedQueue.hpp
@@ -28,8 +28,8 @@ template
class LimitedQueue
{
protected:
- typedef std::shared_ptr> Chunk;
- typedef std::shared_ptr> ChunkVector;
+ using Chunk = std::vector;
+ using ChunkVector = std::vector>;
public:
LimitedQueue(size_t limit = 1000)
@@ -42,9 +42,8 @@ public:
{
std::lock_guard lock(this->mutex_);
- this->chunks_ =
- std::make_shared>>>();
- Chunk chunk = std::make_shared>();
+ this->chunks_ = std::make_shared();
+ auto chunk = std::make_shared();
chunk->resize(this->chunkSize_);
this->chunks_->push_back(chunk);
this->firstChunkOffset_ = 0;
@@ -57,23 +56,21 @@ public:
{
std::lock_guard lock(this->mutex_);
- Chunk lastChunk = this->chunks_->back();
+ auto lastChunk = this->chunks_->back();
- // still space in the last chunk
if (lastChunk->size() <= this->lastChunkEnd_)
{
- // create new chunk vector
- ChunkVector newVector = std::make_shared<
- std::vector>>>();
+ // Last chunk is full, create a new one and rebuild our chunk vector
+ auto newVector = std::make_shared();
// copy chunks
- for (Chunk &chunk : *this->chunks_)
+ for (auto &chunk : *this->chunks_)
{
newVector->push_back(chunk);
}
// push back new chunk
- Chunk newChunk = std::make_shared>();
+ auto newChunk = std::make_shared();
newChunk->resize(this->chunkSize_);
newVector->push_back(newChunk);
@@ -98,8 +95,7 @@ public:
std::lock_guard lock(this->mutex_);
// create new vector to clone chunks into
- ChunkVector newChunks = std::make_shared<
- std::vector>>>();
+ auto newChunks = std::make_shared();
newChunks->resize(this->chunks_->size());
@@ -112,7 +108,7 @@ public:
// create new chunk for the first one
size_t offset =
std::min(this->space(), static_cast(items.size()));
- Chunk newFirstChunk = std::make_shared>();
+ auto newFirstChunk = std::make_shared();
newFirstChunk->resize(this->chunks_->front()->size() + offset);
for (size_t i = 0; i < offset; i++)
@@ -150,7 +146,7 @@ public:
for (size_t i = 0; i < this->chunks_->size(); i++)
{
- Chunk &chunk = this->chunks_->at(i);
+ auto &chunk = this->chunks_->at(i);
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
size_t end =
@@ -160,7 +156,7 @@ public:
{
if (chunk->at(j) == item)
{
- Chunk newChunk = std::make_shared>();
+ auto newChunk = std::make_shared();
newChunk->resize(chunk->size());
for (size_t k = 0; k < chunk->size(); k++)
@@ -189,7 +185,7 @@ public:
for (size_t i = 0; i < this->chunks_->size(); i++)
{
- Chunk &chunk = this->chunks_->at(i);
+ auto &chunk = this->chunks_->at(i);
size_t start = i == 0 ? this->firstChunkOffset_ : 0;
size_t end =
@@ -199,7 +195,7 @@ public:
{
if (x == index)
{
- Chunk newChunk = std::make_shared>();
+ auto newChunk = std::make_shared();
newChunk->resize(chunk->size());
for (size_t k = 0; k < chunk->size(); k++)
@@ -233,7 +229,7 @@ private:
qsizetype space()
{
size_t totalSize = 0;
- for (Chunk &chunk : *this->chunks_)
+ for (auto &chunk : *this->chunks_)
{
totalSize += chunk->size();
}
@@ -257,18 +253,15 @@ private:
deleted = this->chunks_->front()->at(this->firstChunkOffset_);
- this->firstChunkOffset_++;
-
// need to delete the first chunk
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1)
{
// copy the chunk vector
- ChunkVector newVector = std::make_shared<
- std::vector>>>();
+ auto newVector = std::make_shared();
// delete first chunk
bool first = true;
- for (Chunk &chunk : *this->chunks_)
+ for (auto &chunk : *this->chunks_)
{
if (!first)
{
@@ -280,15 +273,20 @@ private:
this->chunks_ = newVector;
this->firstChunkOffset_ = 0;
}
+ else
+ {
+ this->firstChunkOffset_++;
+ }
+
return true;
}
- ChunkVector chunks_;
+ std::shared_ptr chunks_;
std::mutex mutex_;
size_t firstChunkOffset_;
size_t lastChunkEnd_;
- size_t limit_;
+ const size_t limit_;
const size_t chunkSize_ = 100;
};
diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp
index c8efc73d6..c187b5f55 100644
--- a/src/messages/Message.hpp
+++ b/src/messages/Message.hpp
@@ -12,7 +12,7 @@
namespace chatterino {
class MessageElement;
-enum class MessageFlag : uint16_t {
+enum class MessageFlag : uint32_t {
None = 0,
System = (1 << 0),
Timeout = (1 << 1),
@@ -30,6 +30,7 @@ enum class MessageFlag : uint16_t {
DoNotLog = (1 << 13),
AutoMod = (1 << 14),
RecentMessage = (1 << 15),
+ Whisper = (1 << 16)
};
using MessageFlags = FlagsEnum;
@@ -47,6 +48,7 @@ struct Message : boost::noncopyable {
QTime parseTime;
QString id;
QString searchText;
+ QString messageText;
QString loginName;
QString displayName;
QString localizedName;
diff --git a/src/messages/MessageBuilder.cpp b/src/messages/MessageBuilder.cpp
index 2bc8e70dd..96a2d25c6 100644
--- a/src/messages/MessageBuilder.cpp
+++ b/src/messages/MessageBuilder.cpp
@@ -5,6 +5,7 @@
#include "messages/Image.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
+#include "providers/LinkResolver.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
@@ -64,6 +65,8 @@ std::pair makeAutomodMessage(
builder = MessageBuilder();
builder.emplace();
+ builder.emplace();
+ builder.message().loginName = action.target.name;
builder.message().flags.set(MessageFlag::PubSub);
builder
@@ -90,23 +93,30 @@ MessageBuilder::MessageBuilder()
{
}
-MessageBuilder::MessageBuilder(const QString &text)
- : MessageBuilder()
-{
- this->emplace();
- this->emplace(text, MessageElementFlag::Text,
- MessageColor::System);
- this->message().searchText = text;
-}
-
MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text)
: MessageBuilder()
{
this->emplace();
- this->emplace(text, MessageElementFlag::Text,
- MessageColor::System);
+
+ // check system message for links
+ // (e.g. needed for sub ticket message in sub only mode)
+ const QStringList textFragments = text.split(QRegularExpression("\\s"));
+ for (const auto &word : textFragments)
+ {
+ const auto linkString = this->matchLink(word);
+ if (linkString.isEmpty())
+ {
+ this->emplace(word, MessageElementFlag::Text,
+ MessageColor::System);
+ }
+ else
+ {
+ this->addLink(word, linkString);
+ }
+ }
this->message().flags.set(MessageFlag::System);
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
+ this->message().messageText = text;
this->message().searchText = text;
}
@@ -157,6 +167,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
this->emplace();
this->emplace(text, MessageElementFlag::Text,
MessageColor::System);
+ this->message().messageText = text;
this->message().searchText = text;
}
@@ -213,6 +224,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
this->emplace(text, MessageElementFlag::Text,
MessageColor::System);
+ this->message().messageText = text;
this->message().searchText = text;
}
@@ -225,23 +237,15 @@ MessageBuilder::MessageBuilder(const UnbanAction &action)
this->message().timeoutUser = action.target.name;
- QString text;
-
- if (action.wasBan())
- {
- text = QString("%1 unbanned %2.") //
- .arg(action.source.name)
- .arg(action.target.name);
- }
- else
- {
- text = QString("%1 untimedout %2.") //
- .arg(action.source.name)
- .arg(action.target.name);
- }
+ QString text =
+ QString("%1 %2 %3.")
+ .arg(action.source.name)
+ .arg(QString(action.wasBan() ? "unbanned" : "untimedout"))
+ .arg(action.target.name);
this->emplace(text, MessageElementFlag::Text,
MessageColor::System);
+ this->message().messageText = text;
this->message().searchText = text;
}
@@ -352,4 +356,57 @@ QString MessageBuilder::matchLink(const QString &string)
return captured;
}
+void MessageBuilder::addLink(const QString &origLink,
+ const QString &matchedLink)
+{
+ static QRegularExpression domainRegex(
+ R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
+ QRegularExpression::CaseInsensitiveOption);
+
+ QString lowercaseLinkString;
+ auto match = domainRegex.match(origLink);
+ if (match.isValid())
+ {
+ lowercaseLinkString = origLink.mid(0, match.capturedStart(1)) +
+ match.captured(1).toLower() +
+ origLink.mid(match.capturedEnd(1));
+ }
+ else
+ {
+ lowercaseLinkString = origLink;
+ }
+ auto linkElement = Link(Link::Url, matchedLink);
+
+ auto textColor = MessageColor(MessageColor::Link);
+ auto linkMELowercase =
+ this->emplace(lowercaseLinkString,
+ MessageElementFlag::LowercaseLink, textColor)
+ ->setLink(linkElement);
+ auto linkMEOriginal =
+ this->emplace(origLink, MessageElementFlag::OriginalLink,
+ textColor)
+ ->setLink(linkElement);
+
+ LinkResolver::getLinkInfo(matchedLink, [weakMessage = this->weakOf(),
+ linkMELowercase, linkMEOriginal,
+ matchedLink](QString tooltipText,
+ Link originalLink) {
+ auto shared = weakMessage.lock();
+ if (!shared)
+ {
+ return;
+ }
+ if (!tooltipText.isEmpty())
+ {
+ linkMELowercase->setTooltip(tooltipText);
+ linkMEOriginal->setTooltip(tooltipText);
+ }
+ if (originalLink.value != matchedLink && !originalLink.value.isEmpty())
+ {
+ linkMELowercase->setLink(originalLink)->updateLink();
+ linkMEOriginal->setLink(originalLink)->updateLink();
+ }
+ });
+}
+
} // namespace chatterino
diff --git a/src/messages/MessageBuilder.hpp b/src/messages/MessageBuilder.hpp
index 8fe7915e2..4793ce9d9 100644
--- a/src/messages/MessageBuilder.hpp
+++ b/src/messages/MessageBuilder.hpp
@@ -38,7 +38,6 @@ class MessageBuilder
{
public:
MessageBuilder();
- MessageBuilder(const QString &text);
MessageBuilder(SystemMessageTag, const QString &text);
MessageBuilder(TimeoutMessageTag, const QString &username,
const QString &durationInSeconds, const QString &reason,
@@ -54,6 +53,7 @@ public:
void append(std::unique_ptr element);
QString matchLink(const QString &string);
+ void addLink(const QString &origLink, const QString &matchedLink);
template
T *emplace(Args &&... args)
diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp
index 8afe72a5a..e5920fb6f 100644
--- a/src/messages/MessageElement.cpp
+++ b/src/messages/MessageElement.cpp
@@ -138,10 +138,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
if (image->isEmpty())
return;
- auto emoteScale =
- this->getFlags().hasAny(MessageElementFlag::Badges)
- ? 1
- : getSettings()->emoteScale.getValue();
+ auto emoteScale = getSettings()->emoteScale.getValue();
auto size =
QSize(int(container.getScale() * image->width() * emoteScale),
@@ -161,6 +158,31 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
}
}
+// BADGE
+BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
+ : MessageElement(flags)
+ , emote_(emote)
+{
+ this->setTooltip(emote->tooltip.string);
+}
+
+void BadgeElement::addToContainer(MessageLayoutContainer &container,
+ MessageElementFlags flags)
+{
+ if (flags.hasAny(this->getFlags()))
+ {
+ auto image = this->emote_->images.getImage(container.getScale());
+ if (image->isEmpty())
+ return;
+
+ auto size = QSize(int(container.getScale() * image->width()),
+ int(container.getScale() * image->height()));
+
+ container.addElement((new ImageLayoutElement(*this, image, size))
+ ->setLink(this->getLink()));
+ }
+}
+
// TEXT
TextElement::TextElement(const QString &text, MessageElementFlags flags,
const MessageColor &color, FontStyle style)
@@ -188,7 +210,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
for (Word &word : this->words_)
{
auto getTextLayoutElement = [&](QString text, int width,
- bool trailingSpace) {
+ bool hasTrailingSpace) {
auto color = this->color_.getColor(*app->themes);
app->themes->normalizeColor(color);
@@ -196,7 +218,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
*this, text, QSize(width, metrics.height()),
color, this->style_, container.getScale()))
->setLink(this->getLink());
- e->setTrailingSpace(trailingSpace);
+ e->setTrailingSpace(hasTrailingSpace);
e->setText(text);
// If URL link was changed,
@@ -291,7 +313,6 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
{
if (flags.hasAny(this->getFlags()))
{
- auto app = getApp();
if (getSettings()->timestampFormat != this->format_)
{
this->format_ = getSettings()->timestampFormat.getValue();
diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp
index 4ae74d848..95c7090d4 100644
--- a/src/messages/MessageElement.hpp
+++ b/src/messages/MessageElement.hpp
@@ -42,6 +42,7 @@ enum class MessageElementFlag {
FfzEmoteText = (1 << 11),
FfzEmote = FfzEmoteImage | FfzEmoteText,
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
+ EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText,
BitsStatic = (1 << 12),
BitsAnimated = (1 << 13),
@@ -74,9 +75,6 @@ enum class MessageElementFlag {
// - Chatterino top donator badge
BadgeChatterino = (1 << 18),
- // Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke)
- // custom badge?
-
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription |
BadgeVanity | BadgeChatterino,
@@ -216,6 +214,18 @@ private:
EmotePtr emote_;
};
+class BadgeElement : public MessageElement
+{
+public:
+ BadgeElement(const EmotePtr &data, MessageElementFlags flags_);
+
+ void addToContainer(MessageLayoutContainer &container,
+ MessageElementFlags flags_) override;
+
+private:
+ EmotePtr emote_;
+};
+
// 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 e80a1b2c6..f1725f080 100644
--- a/src/messages/layouts/MessageLayout.cpp
+++ b/src/messages/layouts/MessageLayout.cpp
@@ -99,17 +99,14 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
return true;
}
-void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
+void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
{
this->layoutCount_++;
- const auto addTest = this->message_->flags.hasAny(
- {MessageFlag::DisconnectedMessage, MessageFlag::ConnectedMessage});
-
auto messageFlags = this->message_->flags;
if (this->flags.has(MessageLayoutFlag::Expanded) ||
- (_flags.has(MessageElementFlag::ModeratorTools) &&
+ (flags.has(MessageElementFlag::ModeratorTools) &&
!this->message_->flags.has(MessageFlag::Disabled))) //
{
messageFlags.unset(MessageFlag::Collapsed);
@@ -117,25 +114,21 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
this->container_->begin(width, this->scale_, messageFlags);
- if (addTest)
- {
- this->container_->addElementNoLineBreak(new TestLayoutElement(
- EmptyElement::instance(), QSize(width, this->scale_ * 6),
- getTheme()->messages.backgrounds.regular, false));
- this->container_->breakLine();
- }
-
for (const auto &element : this->message_->elements)
{
- element->addToContainer(*this->container_, _flags);
- }
+ if (getSettings()->hideModerated &&
+ this->message_->flags.has(MessageFlag::Disabled))
+ {
+ continue;
+ }
- if (addTest)
- {
- this->container_->breakLine();
- this->container_->addElement(new TestLayoutElement(
- EmptyElement::instance(), QSize(width, this->scale_ * 6),
- getTheme()->messages.backgrounds.regular, true));
+ if (getSettings()->hideModerationActions &&
+ this->message_->flags.has(MessageFlag::Timeout))
+ {
+ continue;
+ }
+
+ element->addToContainer(*this->container_, flags);
}
if (this->height_ != this->container_->getHeight())
@@ -200,29 +193,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
app->themes->messages.disabled);
// painter.fillRect(0, y, pixmap->width(), pixmap->height(),
// QBrush(QColor(64, 64, 64, 64)));
-
- if (getSettings()->redDisabledMessages)
- {
- painter.fillRect(0, y, pixmap->width(), pixmap->height(),
- QBrush(QColor(255, 0, 0, 63), Qt::BDiagPattern));
- // app->themes->messages.disabled);
- }
}
if (this->message_->flags.has(MessageFlag::RecentMessage))
{
- const auto &historicMessageAppearance =
- getSettings()->historicMessagesAppearance.getValue();
- if (historicMessageAppearance & HistoricMessageAppearance::Crossed)
- {
- painter.fillRect(0, y, pixmap->width(), pixmap->height(),
- QBrush(QColor(255, 0, 0, 63), Qt::BDiagPattern));
- }
- if (historicMessageAppearance & HistoricMessageAppearance::Greyed)
- {
- painter.fillRect(0, y, pixmap->width(), pixmap->height(),
- app->themes->messages.disabled);
- }
+ painter.fillRect(0, y, pixmap->width(), pixmap->height(),
+ app->themes->messages.disabled);
}
// draw selection
diff --git a/src/messages/layouts/MessageLayoutContainer.cpp b/src/messages/layouts/MessageLayoutContainer.cpp
index f24d05e26..599564787 100644
--- a/src/messages/layouts/MessageLayoutContainer.cpp
+++ b/src/messages/layouts/MessageLayoutContainer.cpp
@@ -157,7 +157,7 @@ void MessageLayoutContainer::breakLine()
int yExtra = 0;
if (isCompactEmote)
{
- // yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
+ yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
}
// if (element->getCreator().getFlags() &
@@ -390,17 +390,17 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
int lineIndex2 = lineIndex + 1;
for (; lineIndex2 < this->lines_.size(); lineIndex2++)
{
- Line &line = this->lines_[lineIndex2];
- QRect rect = line.rect;
+ Line &line2 = this->lines_[lineIndex2];
+ QRect rect = line2.rect;
rect.setTop(std::max(0, rect.top()) + yOffset);
rect.setBottom(
std::min(this->height_, rect.bottom()) +
yOffset);
- rect.setLeft(this->elements_[line.startIndex]
+ rect.setLeft(this->elements_[line2.startIndex]
->getRect()
.left());
- rect.setRight(this->elements_[line.endIndex - 1]
+ rect.setRight(this->elements_[line2.endIndex - 1]
->getRect()
.right());
diff --git a/src/messages/layouts/MessageLayoutContainer.hpp b/src/messages/layouts/MessageLayoutContainer.hpp
index e248038a3..112f2bb3c 100644
--- a/src/messages/layouts/MessageLayoutContainer.hpp
+++ b/src/messages/layouts/MessageLayoutContainer.hpp
@@ -14,7 +14,7 @@ class QPainter;
namespace chatterino {
-enum class MessageFlag : uint16_t;
+enum class MessageFlag : uint32_t;
using MessageFlags = FlagsEnum;
struct Margin {
diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp
index 93e0afe93..a8bcc30c9 100644
--- a/src/messages/layouts/MessageLayoutElement.cpp
+++ b/src/messages/layouts/MessageLayoutElement.cpp
@@ -368,72 +368,4 @@ int TextIconLayoutElement::getXFromIndex(int index)
}
}
-// TestLayoutElement
-TestLayoutElement::TestLayoutElement(MessageElement &element, const QSize &size,
- const QColor &background, bool end)
- : MessageLayoutElement(element, size)
- , size_(size)
- , background_(background)
- , end_(end)
-{
-}
-
-void TestLayoutElement::addCopyTextToString(QString &str, int from,
- int to) const
-{
-}
-
-int TestLayoutElement::getSelectionIndexCount() const
-{
- return 0;
-}
-
-void TestLayoutElement::paint(QPainter &painter)
-{
- const auto dy = this->getRect().y();
- const auto color = end_ ? background_ : QColor(0, 0, 0, 127);
-
- // make zig zag
- auto polygon = QPolygon();
- for (auto x = size_.height() / -2; x < size_.width() + 16;
- x += size_.height())
- {
- polygon.push_back({x, dy + 0});
- polygon.push_back({x + size_.height(), dy + size_.height()});
- x += size_.height();
- polygon.push_back({x, dy + size_.height()});
- polygon.push_back({x + size_.height(), dy + 0});
- }
-
- // finish polygon
- polygon.push_back({size_.width(), 1000});
- polygon.push_back({0, 1000});
-
- // finish polygon
- polygon.push_back({size_.width(), 1000});
- polygon.push_back({0, 1000});
-
- // turn into path
- auto path = QPainterPath();
- path.addPolygon(polygon);
-
- // draw
- painter.fillPath(path, color);
- painter.strokePath(path, QColor(127, 127, 127, 127));
-}
-
-void TestLayoutElement::paintAnimated(QPainter &painter, int yOffset)
-{
-}
-
-int TestLayoutElement::getMouseOverIndex(const QPoint &abs) const
-{
- return 0;
-}
-
-int TestLayoutElement::getXFromIndex(int index)
-{
- return 0;
-}
-
} // namespace chatterino
diff --git a/src/messages/layouts/MessageLayoutElement.hpp b/src/messages/layouts/MessageLayoutElement.hpp
index 24139be4e..a7214937a 100644
--- a/src/messages/layouts/MessageLayoutElement.hpp
+++ b/src/messages/layouts/MessageLayoutElement.hpp
@@ -41,6 +41,7 @@ public:
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
virtual int getMouseOverIndex(const QPoint &abs) const = 0;
virtual int getXFromIndex(int index) = 0;
+
const Link &getLink() const;
const QString &getText() const;
FlagsEnum getFlags() const;
@@ -125,25 +126,4 @@ private:
QString line2;
};
-class TestLayoutElement : public MessageLayoutElement
-{
-public:
- TestLayoutElement(MessageElement &creator, const QSize &size,
- const QColor &background, bool end);
-
-protected:
- void addCopyTextToString(QString &str, int from = 0,
- int to = INT_MAX) const override;
- int getSelectionIndexCount() const override;
- void paint(QPainter &painter) override;
- void paintAnimated(QPainter &painter, int yOffset) override;
- int getMouseOverIndex(const QPoint &abs) const override;
- int getXFromIndex(int index) override;
-
-private:
- QSize size_;
- QColor background_;
- bool end_;
-};
-
} // namespace chatterino
diff --git a/src/providers/LinkResolver.cpp b/src/providers/LinkResolver.cpp
index d7cc3a1f5..0137a130c 100644
--- a/src/providers/LinkResolver.cpp
+++ b/src/providers/LinkResolver.cpp
@@ -1,6 +1,7 @@
#include "providers/LinkResolver.hpp"
#include "common/Common.hpp"
+#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "messages/Link.hpp"
#include "singletons/Settings.hpp"
@@ -12,12 +13,15 @@ namespace chatterino {
void LinkResolver::getLinkInfo(
const QString url, std::function successCallback)
{
- QString requestUrl("https://braize.pajlada.com/chatterino/link_resolver/" +
- QUrl::toPercentEncoding(url, "", "/:"));
-
+ if (!getSettings()->linkInfoTooltip)
+ {
+ successCallback("No link info loaded", Link(Link::Url, url));
+ return;
+ }
// Uncomment to test crashes
// QTimer::singleShot(3000, [=]() {
- NetworkRequest request(requestUrl);
+ NetworkRequest request(Env::get().linkResolverUrl.arg(
+ QString::fromUtf8(QUrl::toPercentEncoding(url, "", "/:"))));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([successCallback, url](auto result) mutable -> Outcome {
diff --git a/src/providers/emoji/Emojis.cpp b/src/providers/emoji/Emojis.cpp
index d42f619ef..f510d697f 100644
--- a/src/providers/emoji/Emojis.cpp
+++ b/src/providers/emoji/Emojis.cpp
@@ -36,9 +36,9 @@ namespace {
else
{
const auto &shortCodes = unparsedEmoji["short_names"];
- for (const auto &shortCode : shortCodes.GetArray())
+ for (const auto &_shortCode : shortCodes.GetArray())
{
- emojiData->shortCodes.emplace_back(shortCode.GetString());
+ emojiData->shortCodes.emplace_back(_shortCode.GetString());
}
}
@@ -240,8 +240,6 @@ void Emojis::sortEmojis()
void Emojis::loadEmojiSet()
{
- auto app = getApp();
-
getSettings()->emojiSet.connect([=](const auto &emojiSet) {
this->emojis.each([=](const auto &name,
std::shared_ptr &emoji) {
diff --git a/src/providers/irc/AbstractIrcServer.cpp b/src/providers/irc/AbstractIrcServer.cpp
index 7cb45b3c7..eb1d0def9 100644
--- a/src/providers/irc/AbstractIrcServer.cpp
+++ b/src/providers/irc/AbstractIrcServer.cpp
@@ -231,9 +231,8 @@ void AbstractIrcServer::onConnected()
{
std::lock_guard lock(this->channelMutex);
- auto connected = makeSystemMessage("connected");
- connected->flags.set(MessageFlag::ConnectedMessage);
- connected->flags.set(MessageFlag::Centered);
+ auto connectedMsg = makeSystemMessage("connected");
+ connectedMsg->flags.set(MessageFlag::ConnectedMessage);
auto reconnected = makeSystemMessage("reconnected");
reconnected->flags.set(MessageFlag::ConnectedMessage);
@@ -257,7 +256,7 @@ void AbstractIrcServer::onConnected()
continue;
}
- chan->addMessage(connected);
+ chan->addMessage(connectedMsg);
}
this->falloffCounter_ = 1;
@@ -269,7 +268,7 @@ void AbstractIrcServer::onDisconnected()
MessageBuilder b(systemMessage, "disconnected");
b->flags.set(MessageFlag::DisconnectedMessage);
- auto disconnected = b.release();
+ auto disconnectedMsg = b.release();
for (std::weak_ptr &weak : this->channels.values())
{
@@ -279,7 +278,7 @@ void AbstractIrcServer::onDisconnected()
continue;
}
- chan->addMessage(disconnected);
+ chan->addMessage(disconnectedMsg);
}
}
diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp
index 27df7cb8c..d6f091a76 100644
--- a/src/providers/twitch/IrcMessageHandler.cpp
+++ b/src/providers/twitch/IrcMessageHandler.cpp
@@ -20,12 +20,69 @@
namespace chatterino {
+static QMap parseBadges(QString badgesString)
+{
+ QMap badges;
+
+ for (auto badgeData : badgesString.split(','))
+ {
+ auto parts = badgeData.split('/');
+ if (parts.length() != 2)
+ {
+ continue;
+ }
+
+ badges.insert(parts[0], parts[1]);
+ }
+
+ return badges;
+}
+
IrcMessageHandler &IrcMessageHandler::getInstance()
{
static IrcMessageHandler instance;
return instance;
}
+std::vector IrcMessageHandler::parseMessage(
+ Channel *channel, Communi::IrcMessage *message)
+{
+ std::vector builtMessages;
+
+ auto command = message->command();
+
+ if (command == "PRIVMSG")
+ {
+ return this->parsePrivMessage(
+ channel, static_cast(message));
+ }
+ else if (command == "USERNOTICE")
+ {
+ return this->parseUserNoticeMessage(channel, message);
+ }
+ else if (command == "NOTICE")
+ {
+ return this->parseNoticeMessage(
+ static_cast(message));
+ }
+
+ return builtMessages;
+}
+
+std::vector IrcMessageHandler::parsePrivMessage(
+ Channel *channel, Communi::IrcPrivateMessage *message)
+{
+ std::vector builtMessages;
+ MessageParseArgs args;
+ TwitchMessageBuilder builder(channel, message, args, message->content(),
+ message->isAction());
+ if (!builder.isIgnored())
+ {
+ builtMessages.emplace_back(builder.build());
+ }
+ return builtMessages;
+}
+
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchServer &server)
{
@@ -203,28 +260,78 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
// refresh all
app->windows->repaintVisibleChatWidgets(chan.get());
+ if (getSettings()->hideModerated)
+ {
+ app->windows->forceLayoutChannelViews();
+ }
+}
+
+void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message)
+{
+ // check parameter count
+ if (message->parameters().length() < 1)
+ {
+ return;
+ }
+
+ QString chanName;
+ if (!trimChannelName(message->parameter(0), chanName))
+ {
+ return;
+ }
+
+ auto app = getApp();
+
+ // get channel
+ auto chan = app->twitch.server->getChannelOrEmpty(chanName);
+
+ if (chan->isEmpty())
+ {
+ log("[IrcMessageHandler:handleClearMessageMessage] Twitch channel {} "
+ "not "
+ "found",
+ chanName);
+ return;
+ }
+
+ auto tags = message->tags();
+
+ QString targetID = tags.value("target-msg-id").toString();
+
+ chan->deleteMessage(targetID);
}
void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
{
- QVariant _mod = message->tag("mod");
+ auto app = getApp();
+ QString channelName;
+ if (!trimChannelName(message->parameter(0), channelName))
+ {
+ return;
+ }
+
+ auto c = app->twitch.server->getChannelOrEmpty(channelName);
+ if (c->isEmpty())
+ {
+ return;
+ }
+
+ QVariant _badges = message->tag("badges");
+ if (_badges.isValid())
+ {
+ TwitchChannel *tc = dynamic_cast(c.get());
+ if (tc != nullptr)
+ {
+ auto parsedBadges = parseBadges(_badges.toString());
+ tc->setVIP(parsedBadges.contains("vip"));
+ tc->setStaff(parsedBadges.contains("staff"));
+ }
+ }
+
+ QVariant _mod = message->tag("mod");
if (_mod.isValid())
{
- auto app = getApp();
-
- QString channelName;
- if (!trimChannelName(message->parameter(0), channelName))
- {
- return;
- }
-
- auto c = app->twitch.server->getChannelOrEmpty(channelName);
- if (c->isEmpty())
- {
- return;
- }
-
TwitchChannel *tc = dynamic_cast(c.get());
if (tc != nullptr)
{
@@ -248,6 +355,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
if (!builder.isIgnored())
{
+ builder->flags.set(MessageFlag::Whisper);
MessagePtr _message = builder.build();
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
@@ -273,6 +381,56 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
}
}
+std::vector IrcMessageHandler::parseUserNoticeMessage(
+ Channel *channel, Communi::IrcMessage *message)
+{
+ std::vector builtMessages;
+
+ auto data = message->toData();
+
+ auto tags = message->tags();
+ auto parameters = message->parameters();
+
+ auto target = parameters[0];
+ QString msgType = tags.value("msg-id", "").toString();
+ QString content;
+ if (parameters.size() >= 2)
+ {
+ content = parameters[1];
+ }
+
+ if (msgType == "sub" || msgType == "resub" || msgType == "subgift")
+ {
+ // Sub-specific message. I think it's only allowed for "resub" messages
+ // atm
+ if (!content.isEmpty())
+ {
+ MessageParseArgs args;
+ args.trimSubscriberUsername = true;
+
+ TwitchMessageBuilder builder(channel, message, args, content,
+ false);
+ builder->flags.set(MessageFlag::Subscription);
+ builder->flags.unset(MessageFlag::Highlighted);
+ builtMessages.emplace_back(builder.build());
+ }
+ }
+
+ auto it = tags.find("system-msg");
+
+ if (it != tags.end())
+ {
+ auto b = MessageBuilder(systemMessage,
+ parseTagString(it.value().toString()));
+
+ b->flags.set(MessageFlag::Subscription);
+ auto newMessage = b.release();
+ builtMessages.emplace_back(newMessage);
+ }
+
+ return builtMessages;
+}
+
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchServer &server)
{
@@ -352,35 +510,60 @@ void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
}
}
+std::vector IrcMessageHandler::parseNoticeMessage(
+ Communi::IrcNoticeMessage *message)
+{
+ std::vector builtMessages;
+
+ builtMessages.emplace_back(makeSystemMessage(message->content()));
+
+ return builtMessages;
+}
+
void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
{
auto app = getApp();
- MessagePtr msg = makeSystemMessage(message->content());
+ auto builtMessages = this->parseNoticeMessage(message);
- QString channelName;
- if (!trimChannelName(message->target(), channelName))
+ for (auto msg : builtMessages)
{
- // Notice wasn't targeted at a single channel, send to all twitch
- // channels
- app->twitch.server->forEachChannelAndSpecialChannels(
- [msg](const auto &c) {
- c->addMessage(msg); //
- });
+ QString channelName;
+ if (!trimChannelName(message->target(), channelName) ||
+ channelName == "jtv")
+ {
+ // Notice wasn't targeted at a single channel, send to all twitch
+ // channels
+ app->twitch.server->forEachChannelAndSpecialChannels(
+ [msg](const auto &c) {
+ c->addMessage(msg); //
+ });
- return;
+ return;
+ }
+
+ auto channel = app->twitch.server->getChannelOrEmpty(channelName);
+
+ if (channel->isEmpty())
+ {
+ log("[IrcManager:handleNoticeMessage] Channel {} not found in "
+ "channel "
+ "manager ",
+ channelName);
+ return;
+ }
+
+ QString tags = message->tags().value("msg-id", "").toString();
+ if (tags == "bad_delete_message_error" || tags == "usage_delete")
+ {
+ channel->addMessage(makeSystemMessage(
+ "Usage: \"/delete \" - can't take more "
+ "than one argument"));
+ }
+ else
+ {
+ channel->addMessage(msg);
+ }
}
-
- auto channel = app->twitch.server->getChannelOrEmpty(channelName);
-
- if (channel->isEmpty())
- {
- log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
- "manager ",
- channelName);
- return;
- }
-
- channel->addMessage(msg);
}
void IrcMessageHandler::handleWriteConnectionNoticeMessage(
diff --git a/src/providers/twitch/IrcMessageHandler.hpp b/src/providers/twitch/IrcMessageHandler.hpp
index 40f1efb12..861ee734e 100644
--- a/src/providers/twitch/IrcMessageHandler.hpp
+++ b/src/providers/twitch/IrcMessageHandler.hpp
@@ -1,10 +1,12 @@
#pragma once
#include
+#include "messages/Message.hpp"
namespace chatterino {
class TwitchServer;
+class Channel;
class IrcMessageHandler
{
@@ -13,17 +15,37 @@ class IrcMessageHandler
public:
static IrcMessageHandler &getInstance();
+ // parseMessage parses a single IRC message into 0+ Chatterino messages
+ std::vector parseMessage(Channel *channel,
+ Communi::IrcMessage *message);
+
+ // parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
+ std::vector parsePrivMessage(
+ Channel *channel, Communi::IrcPrivateMessage *message);
void handlePrivMessage(Communi::IrcPrivateMessage *message,
TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message);
+ void handleClearMessageMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message);
+
+ // parseUserNoticeMessage parses a single IRC USERNOTICE message into 0+
+ // chatterino messages
+ std::vector parseUserNoticeMessage(
+ Channel *channel, Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message,
TwitchServer &server);
+
void handleModeMessage(Communi::IrcMessage *message);
+
+ // parseNoticeMessage parses a single IRC NOTICE message into 0+ chatterino
+ // messages
+ std::vector parseNoticeMessage(
+ Communi::IrcNoticeMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
+
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
diff --git a/src/providers/twitch/TwitchAccount.cpp b/src/providers/twitch/TwitchAccount.cpp
index 1d798f8ce..c397a2248 100644
--- a/src/providers/twitch/TwitchAccount.cpp
+++ b/src/providers/twitch/TwitchAccount.cpp
@@ -3,6 +3,7 @@
#include
#include "Application.hpp"
+#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
@@ -534,9 +535,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr emoteSet)
return;
}
- NetworkRequest req(
- "https://braize.pajlada.com/chatterino/twitchemotes/set/" +
- emoteSet->key + "/");
+ NetworkRequest req(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key));
req.setUseQuickLoadCache(true);
req.onError([](int errorCode) -> bool {
diff --git a/src/providers/twitch/TwitchAccountManager.cpp b/src/providers/twitch/TwitchAccountManager.cpp
index a42c344da..5b7a2c431 100644
--- a/src/providers/twitch/TwitchAccountManager.cpp
+++ b/src/providers/twitch/TwitchAccountManager.cpp
@@ -172,8 +172,6 @@ bool TwitchAccountManager::isLoggedIn() const
bool TwitchAccountManager::removeUser(TwitchAccount *account)
{
- const auto &accs = this->accounts.getVector();
-
auto userID(account->getUserId());
if (!userID.isEmpty())
{
diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp
index a569de5ae..b8ce6509b 100644
--- a/src/providers/twitch/TwitchChannel.cpp
+++ b/src/providers/twitch/TwitchChannel.cpp
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "common/Common.hpp"
+#include "common/Env.hpp"
#include "common/NetworkRequest.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/notifications/NotificationController.hpp"
@@ -9,6 +10,7 @@
#include "messages/Message.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/bttv/LoadBttvChannelEmote.hpp"
+#include "providers/twitch/IrcMessageHandler.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
@@ -29,10 +31,14 @@
namespace chatterino {
namespace {
+ constexpr char MAGIC_MESSAGE_SUFFIX[] = u8" \U000E0000";
+
+ // parseRecentMessages takes a json object and returns a vector of
+ // Communi IrcMessages
auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel)
{
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
- std::vector messages;
+ std::vector messages;
if (jsonMessages.empty())
return messages;
@@ -40,26 +46,17 @@ namespace {
for (const auto jsonMessage : jsonMessages)
{
auto content = jsonMessage.toString().toUtf8();
- // passing nullptr as the channel makes the message invalid but we
- // don't check for that anyways
- auto message = Communi::IrcMessage::fromData(content, nullptr);
- auto privMsg = dynamic_cast(message);
- assert(privMsg);
-
- MessageParseArgs args;
- TwitchMessageBuilder builder(channel.get(), privMsg, args);
- builder.message().flags.set(MessageFlag::RecentMessage);
-
- if (!builder.isIgnored())
- messages.push_back(builder.build());
+ messages.emplace_back(
+ Communi::IrcMessage::fromData(content, nullptr));
}
return messages;
}
std::pair parseChatters(const QJsonObject &jsonRoot)
{
- static QStringList categories = {"moderators", "staff", "admins",
- "global_mods", "viewers"};
+ static QStringList categories = {"broadcaster", "vips", "moderators",
+ "staff", "admins", "global_mods",
+ "viewers"};
auto usernames = UsernameSet();
@@ -127,10 +124,6 @@ TwitchChannel::TwitchChannel(const QString &name,
[=] { this->refreshLiveStatus(); });
this->liveStatusTimer_.start(60 * 1000);
- // --
- this->messageSuffix_.append(' ');
- this->messageSuffix_.append(QChar(0x206D));
-
// debugging
#if 0
for (int i = 0; i < 1000; i++) {
@@ -199,13 +192,13 @@ void TwitchChannel::sendMessage(const QString &message)
return;
}
- if (!this->hasModRights())
+ if (!this->hasHighRateLimit())
{
if (getSettings()->allowDuplicateMessages)
{
if (parsedMessage == this->lastSentMessage_)
{
- parsedMessage.append(this->messageSuffix_);
+ parsedMessage.append(MAGIC_MESSAGE_SUFFIX);
}
}
}
@@ -225,6 +218,16 @@ bool TwitchChannel::isMod() const
return this->mod_;
}
+bool TwitchChannel::isVIP() const
+{
+ return this->vip_;
+}
+
+bool TwitchChannel::isStaff() const
+{
+ return this->staff_;
+}
+
void TwitchChannel::setMod(bool value)
{
if (this->mod_ != value)
@@ -235,6 +238,26 @@ void TwitchChannel::setMod(bool value)
}
}
+void TwitchChannel::setVIP(bool value)
+{
+ if (this->vip_ != value)
+ {
+ this->vip_ = value;
+
+ this->userStateChanged.invoke();
+ }
+}
+
+void TwitchChannel::setStaff(bool value)
+{
+ if (this->staff_ != value)
+ {
+ this->staff_ = value;
+
+ this->userStateChanged.invoke();
+ }
+}
+
bool TwitchChannel::isBroadcaster() const
{
auto app = getApp();
@@ -242,6 +265,11 @@ bool TwitchChannel::isBroadcaster() const
return this->getName() == app->accounts->twitch.getCurrent()->getUserName();
}
+bool TwitchChannel::hasHighRateLimit() const
+{
+ return this->isMod() || this->isBroadcaster() || this->isVIP();
+}
+
void TwitchChannel::addRecentChatter(const MessagePtr &message)
{
this->chatters_.access()->insert(message->displayName);
@@ -445,7 +473,7 @@ void TwitchChannel::setLive(bool newLiveStatus)
else
{
auto offline =
- makeSystemMessage(this->getName() + " is offline");
+ makeSystemMessage(this->getDisplayName() + " is offline");
this->addMessage(offline);
}
guard->live = newLiveStatus;
@@ -575,12 +603,13 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
void TwitchChannel::loadRecentMessages()
{
- static QString genericURL =
- "https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" +
- getDefaultClientID();
+ if (!getSettings()->loadTwitchMessageHistoryOnConnect)
+ {
+ return;
+ }
- NetworkRequest request(genericURL.arg(this->roomId()));
- request.makeAuthorizedV5(getDefaultClientID());
+ NetworkRequest request(
+ Env::get().recentMessagesApiUrl.arg(this->getName()));
request.setCaller(QThread::currentThread());
// can't be concurrent right now due to SignalVector
// request.setExecuteConcurrently(true);
@@ -592,7 +621,21 @@ void TwitchChannel::loadRecentMessages()
auto messages = parseRecentMessages(result.parseJson(), shared);
- shared->addMessagesAtStart(messages);
+ auto &handler = IrcMessageHandler::getInstance();
+
+ std::vector allBuiltMessages;
+
+ for (auto message : messages)
+ {
+ for (auto builtMessage :
+ handler.parseMessage(shared.get(), message))
+ {
+ builtMessage->flags.set(MessageFlag::RecentMessage);
+ allBuiltMessages.emplace_back(builtMessage);
+ }
+ }
+
+ shared->addMessagesAtStart(allBuiltMessages);
return Success;
});
@@ -618,11 +661,11 @@ void TwitchChannel::refreshChatters()
{
// setting?
const auto streamStatus = this->accessStreamStatus();
-
+ const auto viewerCount = static_cast(streamStatus->viewerCount);
if (getSettings()->onlyFetchChattersForSmallerStreamers)
{
if (streamStatus->live &&
- streamStatus->viewerCount > getSettings()->smallStreamerLimit)
+ viewerCount > getSettings()->smallStreamerLimit)
{
return;
}
@@ -674,8 +717,8 @@ void TwitchChannel::refreshBadges()
{
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
- auto _ = jsonBadgeSet->toObject()["versions"].toObject();
- for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
+ auto _set = jsonBadgeSet->toObject()["versions"].toObject();
+ for (auto jsonVersion_ = _set.begin(); jsonVersion_ != _set.end();
jsonVersion_++)
{
auto jsonVersion = jsonVersion_->toObject();
diff --git a/src/providers/twitch/TwitchChannel.hpp b/src/providers/twitch/TwitchChannel.hpp
index 0383cd43d..9c4089ff0 100644
--- a/src/providers/twitch/TwitchChannel.hpp
+++ b/src/providers/twitch/TwitchChannel.hpp
@@ -61,7 +61,10 @@ public:
virtual bool canSendMessage() const override;
virtual void sendMessage(const QString &message) override;
virtual bool isMod() const override;
+ bool isVIP() const;
+ bool isStaff() const;
virtual bool isBroadcaster() const override;
+ virtual bool hasHighRateLimit() const override;
// Data
const QString &subscriptionUrl();
@@ -123,6 +126,8 @@ private:
void addPartedUser(const QString &user);
void setLive(bool newLiveStatus);
void setMod(bool value);
+ void setVIP(bool value);
+ void setStaff(bool value);
void setRoomId(const QString &id);
void setRoomModes(const RoomModes &roomModes_);
@@ -151,6 +156,8 @@ private:
FfzModBadge ffzCustomModBadge_;
bool mod_ = false;
+ bool vip_ = false;
+ bool staff_ = false;
UniqueAccess roomID_;
UniqueAccess joinedUsers_;
@@ -159,7 +166,6 @@ private:
bool partedUsersMergeQueued_ = false;
// --
- QByteArray messageSuffix_;
QString lastSentMessage_;
QObject lifetimeGuard_;
QTimer liveStatusTimer_;
diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp
index b8c34cc65..b68eb8d8a 100644
--- a/src/providers/twitch/TwitchMessageBuilder.cpp
+++ b/src/providers/twitch/TwitchMessageBuilder.cpp
@@ -4,9 +4,9 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/ignores/IgnoreController.hpp"
+#include "controllers/pings/PingController.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
-#include "providers/LinkResolver.hpp"
#include "providers/chatterino/ChatterinoBadges.hpp"
#include "providers/twitch/TwitchBadges.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@@ -80,6 +80,19 @@ bool TwitchMessageBuilder::isIgnored() const
{
if (sourceUserID == user.id)
{
+ switch (static_cast(
+ getSettings()->showIgnoredUsersMessages.getValue()))
+ {
+ case ShowIgnoredUsersMessages::IfModerator:
+ if (this->channel->isMod() ||
+ this->channel->isBroadcaster())
+ return false;
+ break;
+ case ShowIgnoredUsersMessages::IfBroadcaster:
+ if (this->channel->isBroadcaster())
+ return false;
+ break;
+ }
log("Blocking message because it's from blocked user {}",
user.name);
return true;
@@ -109,12 +122,24 @@ MessagePtr TwitchMessageBuilder::build()
this->appendChannelName();
+ if (this->tags.contains("rm-deleted"))
+ {
+ this->message().flags.set(MessageFlag::Disabled);
+ }
+
// timestamp
bool isPastMsg = this->tags.contains("historical");
if (isPastMsg)
{
// This may be architecture dependent(datatype)
- qint64 ts = this->tags.value("tmi-sent-ts").toLongLong();
+ bool customReceived = false;
+ qint64 ts =
+ this->tags.value("rm-received-ts").toLongLong(&customReceived);
+ if (!customReceived)
+ {
+ ts = this->tags.value("tmi-sent-ts").toLongLong();
+ }
+
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
this->emplace(dateTime.time());
}
@@ -310,13 +335,13 @@ MessagePtr TwitchMessageBuilder::build()
QRegularExpression emoteregex(
"\\b" + std::get<2>(tup).string + "\\b",
QRegularExpression::UseUnicodePropertiesOption);
- auto match = emoteregex.match(midExtendedRef);
- if (match.hasMatch())
+ auto _match = emoteregex.match(midExtendedRef);
+ if (_match.hasMatch())
{
- int last = match.lastCapturedIndex();
+ int last = _match.lastCapturedIndex();
for (int i = 0; i <= last; ++i)
{
- std::get<0>(tup) = from + match.capturedStart();
+ std::get<0>(tup) = from + _match.capturedStart();
twitchEmotes.push_back(std::move(tup));
}
}
@@ -414,7 +439,9 @@ MessagePtr TwitchMessageBuilder::build()
this->addWords(splits, twitchEmotes);
- this->message().searchText = this->userName + ": " + this->originalMessage_;
+ this->message().messageText = this->originalMessage_;
+ this->message().searchText = this->message().localizedName + " " +
+ this->userName + ": " + this->originalMessage_;
return this->release();
}
@@ -512,56 +539,7 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
}
else
{
- static QRegularExpression domainRegex(
- R"(^(?:(?:ftp|http)s?:\/\/)?([^\/]+)(?:\/.*)?$)",
- QRegularExpression::CaseInsensitiveOption);
-
- QString lowercaseLinkString;
- auto match = domainRegex.match(string);
- if (match.isValid())
- {
- lowercaseLinkString = string.mid(0, match.capturedStart(1)) +
- match.captured(1).toLower() +
- string.mid(match.capturedEnd(1));
- }
- else
- {
- lowercaseLinkString = string;
- }
- link = Link(Link::Url, linkString);
-
- textColor = MessageColor(MessageColor::Link);
- auto linkMELowercase =
- this->emplace(lowercaseLinkString,
- MessageElementFlag::LowercaseLink,
- textColor)
- ->setLink(link);
- auto linkMEOriginal =
- this->emplace(string, MessageElementFlag::OriginalLink,
- textColor)
- ->setLink(link);
-
- LinkResolver::getLinkInfo(
- linkString,
- [weakMessage = this->weakOf(), linkMELowercase, linkMEOriginal,
- linkString](QString tooltipText, Link originalLink) {
- auto shared = weakMessage.lock();
- if (!shared)
- {
- return;
- }
- if (!tooltipText.isEmpty())
- {
- linkMELowercase->setTooltip(tooltipText);
- linkMEOriginal->setTooltip(tooltipText);
- }
- if (originalLink.value != linkString &&
- !originalLink.value.isEmpty())
- {
- linkMELowercase->setLink(originalLink)->updateLink();
- linkMEOriginal->setLink(originalLink)->updateLink();
- }
- });
+ this->addLink(string, linkString);
}
// if (!linkString.isEmpty()) {
@@ -605,7 +583,7 @@ void TwitchMessageBuilder::parseMessageID()
if (iterator != this->tags.end())
{
- this->messageID = iterator.value().toString();
+ this->message().id = iterator.value().toString();
}
}
@@ -632,7 +610,7 @@ void TwitchMessageBuilder::parseRoomID()
void TwitchMessageBuilder::appendChannelName()
{
QString channelName("#" + this->channel->getName());
- Link link(Link::Url, this->channel->getName() + "\n" + this->messageID);
+ Link link(Link::Url, this->channel->getName() + "\n" + this->message().id);
this->emplace(channelName, MessageElementFlag::ChannelName,
MessageColor::System) //
@@ -805,16 +783,10 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
}
// update the media player url if necessary
- QUrl highlightSoundUrl;
- if (getSettings()->customHighlightSound)
- {
- highlightSoundUrl =
- QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
- }
- else
- {
- highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
- }
+ QUrl highlightSoundUrl =
+ getSettings()->customHighlightSound
+ ? QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue())
+ : QUrl("qrc:/sounds/ping2.wav");
if (currentPlayerUrl != highlightSoundUrl)
{
@@ -916,13 +888,16 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
if (!isPastMsg)
{
- if (playSound &&
- (!hasFocus || getSettings()->highlightAlwaysPlaySound))
+ bool notMuted = !getApp()->pings->isMuted(this->channel->getName());
+ bool resolveFocus =
+ !hasFocus || getSettings()->highlightAlwaysPlaySound;
+
+ if (playSound && notMuted && resolveFocus)
{
player->play();
}
- if (doAlert)
+ if (doAlert && notMuted)
{
getApp()->windows->sendAlert();
}
@@ -983,52 +958,36 @@ void TwitchMessageBuilder::appendTwitchEmote(
Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
{
- // Special channels, like /whispers and /channels return here
- // This means they will not render any BTTV or FFZ emotes
- if (this->twitchChannel == nullptr)
- {
- auto *app = getApp();
- const auto &bttvemotes = app->twitch.server->getBttvEmotes();
- const auto &ffzemotes = app->twitch.server->getFfzEmotes();
- auto flags = MessageElementFlags();
- auto emote = boost::optional{};
- { // bttv/ffz emote
- if ((emote = bttvemotes.emote(name)))
- {
- flags = MessageElementFlag::BttvEmote;
- }
- else if ((emote = ffzemotes.emote(name)))
- {
- flags = MessageElementFlag::FfzEmote;
- }
- if (emote)
- {
- this->emplace(emote.get(), flags);
- return Success;
- }
- } // bttv/ffz emote
- return Failure;
- }
+ auto *app = getApp();
+
+ const auto &globalBttvEmotes = app->twitch.server->getBttvEmotes();
+ const auto &globalFfzEmotes = app->twitch.server->getFfzEmotes();
auto flags = MessageElementFlags();
auto emote = boost::optional{};
- if ((emote = this->twitchChannel->globalBttv().emote(name)))
- {
- flags = MessageElementFlag::BttvEmote;
- }
- else if ((emote = this->twitchChannel->bttvEmote(name)))
- {
- flags = MessageElementFlag::BttvEmote;
- }
- else if ((emote = this->twitchChannel->globalFfz().emote(name)))
+ // Emote order:
+ // - FrankerFaceZ Channel
+ // - BetterTTV Channel
+ // - FrankerFaceZ Global
+ // - BetterTTV Global
+ if (this->twitchChannel && (emote = this->twitchChannel->ffzEmote(name)))
{
flags = MessageElementFlag::FfzEmote;
}
- else if ((emote = this->twitchChannel->ffzEmote(name)))
+ else if (this->twitchChannel &&
+ (emote = this->twitchChannel->bttvEmote(name)))
+ {
+ flags = MessageElementFlag::BttvEmote;
+ }
+ else if ((emote = globalFfzEmotes.emote(name)))
{
flags = MessageElementFlag::FfzEmote;
}
+ else if ((emote = globalBttvEmotes.emote(name)))
+ {
+ flags = MessageElementFlag::BttvEmote;
+ }
if (emote)
{
@@ -1064,11 +1023,11 @@ void TwitchMessageBuilder::appendTwitchBadges()
try
{
if (twitchChannel)
- if (const auto &badge = this->twitchChannel->twitchBadge(
+ if (const auto &_badge = this->twitchChannel->twitchBadge(
"bits", cheerAmount))
{
- this->emplace(
- badge.get(), MessageElementFlag::BadgeVanity)
+ this->emplace(
+ _badge.get(), MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
continue;
}
@@ -1079,10 +1038,10 @@ void TwitchMessageBuilder::appendTwitchBadges()
}
// Use default bit badge
- if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
+ if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
"bits", cheerAmount))
{
- this->emplace(badge.get(),
+ this->emplace(_badge.get(),
MessageElementFlag::BadgeVanity)
->setTooltip(tooltip);
}
@@ -1112,7 +1071,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
{
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
{
- this->emplace(
+ this->emplace(
customModBadge.get(),
MessageElementFlag::BadgeChannelAuthority)
->setTooltip((*customModBadge)->tooltip.string);
@@ -1172,7 +1131,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
if (auto badgeEmote = this->twitchChannel->twitchBadge(
"subscriber", badge.mid(11)))
{
- this->emplace(
+ this->emplace(
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
->setTooltip((*badgeEmote)->tooltip.string);
continue;
@@ -1193,17 +1152,17 @@ void TwitchMessageBuilder::appendTwitchBadges()
if (auto badgeEmote =
this->twitchChannel->twitchBadge(splits[0], splits[1]))
{
- this->emplace(badgeEmote.get(),
+ this->emplace(badgeEmote.get(),
MessageElementFlag::BadgeVanity)
->setTooltip((*badgeEmote)->tooltip.string);
continue;
}
- if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
+ if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
splits[0], splits[1]))
{
- this->emplace(badge.get(),
+ this->emplace(_badge.get(),
MessageElementFlag::BadgeVanity)
- ->setTooltip((*badge)->tooltip.string);
+ ->setTooltip((*_badge)->tooltip.string);
continue;
}
}
@@ -1216,7 +1175,7 @@ void TwitchMessageBuilder::appendChatterinoBadges()
getApp()->chatterinoBadges->getBadge({this->userName});
if (chatterinoBadgePtr)
{
- this->emplace(*chatterinoBadgePtr,
+ this->emplace(*chatterinoBadgePtr,
MessageElementFlag::BadgeChatterino);
}
}
diff --git a/src/providers/twitch/TwitchMessageBuilder.hpp b/src/providers/twitch/TwitchMessageBuilder.hpp
index 5aa72b204..da7b3865c 100644
--- a/src/providers/twitch/TwitchMessageBuilder.hpp
+++ b/src/providers/twitch/TwitchMessageBuilder.hpp
@@ -41,7 +41,6 @@ public:
MessageParseArgs args;
const QVariantMap tags;
- QString messageID;
QString userName;
bool isIgnored() const;
@@ -55,8 +54,10 @@ private:
void appendUsername();
void parseHighlights(bool isPastMsg);
- void appendTwitchEmote(const QString &emote,
- std::vector> &vec, std::vector &correctPositions);
+ void appendTwitchEmote(
+ const QString &emote,
+ std::vector> &vec,
+ std::vector &correctPositions);
Outcome tryAppendEmote(const EmoteName &name);
void addWords(
diff --git a/src/providers/twitch/TwitchServer.cpp b/src/providers/twitch/TwitchServer.cpp
index b12a46190..58f98494c 100644
--- a/src/providers/twitch/TwitchServer.cpp
+++ b/src/providers/twitch/TwitchServer.cpp
@@ -156,6 +156,10 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
{
handler.handleClearChatMessage(message);
}
+ else if (command == "CLEARMSG")
+ {
+ handler.handleClearMessageMessage(message);
+ }
else if (command == "USERSTATE")
{
handler.handleUserStateMessage(message);
@@ -219,7 +223,7 @@ std::shared_ptr TwitchServer::getCustomChannel(
{
static auto channel =
std::make_shared("$$$", chatterino::Channel::Type::Misc);
- static auto timer = [&] {
+ static auto getTimer = [&] {
for (auto i = 0; i < 1000; i++)
{
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
@@ -264,7 +268,8 @@ std::shared_ptr TwitchServer::getChannelOrEmptyByID(
if (!twitchChannel)
continue;
- if (twitchChannel->roomId() == channelId)
+ if (twitchChannel->roomId() == channelId &&
+ twitchChannel->getName().splitRef(":").size() < 3)
{
return twitchChannel;
}
@@ -293,10 +298,11 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
std::lock_guard guard(this->lastMessageMutex_);
// std::queue
- auto &lastMessage = channel->hasModRights() ? this->lastMessageMod_
- : this->lastMessagePleb_;
- size_t maxMessageCount = channel->hasModRights() ? 99 : 19;
- auto minMessageOffset = (channel->hasModRights() ? 100ms : 1100ms);
+ auto &lastMessage = channel->hasHighRateLimit()
+ ? this->lastMessageMod_
+ : this->lastMessagePleb_;
+ size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
+ auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
auto now = std::chrono::steady_clock::now();
diff --git a/src/singletons/Paths.cpp b/src/singletons/Paths.cpp
index 3114cf2d6..0ba547135 100644
--- a/src/singletons/Paths.cpp
+++ b/src/singletons/Paths.cpp
@@ -37,7 +37,7 @@ bool Paths::isPortable()
QString Paths::cacheDirectory()
{
- static QStringSetting cachePathSetting = [] {
+ static const auto pathSetting = [] {
QStringSetting cachePathSetting("/cache/path");
cachePathSetting.connect([](const auto &newPath, auto) {
@@ -47,9 +47,9 @@ QString Paths::cacheDirectory()
return cachePathSetting;
}();
- auto path = cachePathSetting.getValue();
+ auto path = pathSetting.getValue();
- if (path == "")
+ if (path.isEmpty())
{
return this->cacheDirectory_;
}
diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp
index 953374193..602232371 100644
--- a/src/singletons/Settings.hpp
+++ b/src/singletons/Settings.hpp
@@ -4,7 +4,7 @@
#include "controllers/highlights/HighlightPhrase.hpp"
#include "controllers/moderationactions/ModerationAction.hpp"
-#include "messages/HistoricMessageAppearance.hpp"
+#include "singletons/Toasts.hpp"
#include
#include
@@ -32,15 +32,15 @@ public:
Qt::VerPattern};
QStringSetting lastMessageColor = {"/appearance/messages/lastMessageColor",
""};
- IntSetting historicMessagesAppearance = {
- "/appearance/messages/historicMessagesAppearance",
- HistoricMessageAppearance::Crossed | HistoricMessageAppearance::Greyed};
BoolSetting showEmptyInput = {"/appearance/showEmptyInputBox", true};
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength",
false};
BoolSetting separateMessages = {"/appearance/messages/separateMessages",
false};
BoolSetting compactEmotes = {"/appearance/messages/compactEmotes", true};
+ BoolSetting hideModerated = {"/appearance/messages/hideModerated", false};
+ BoolSetting hideModerationActions = {
+ "/appearance/messages/hideModerationActions", false};
// BoolSetting collapseLongMessages =
// {"/appearance/messages/collapseLongMessages", false};
@@ -48,7 +48,7 @@ public:
"/appearance/messages/collapseMessagesMinLines", 0};
BoolSetting alternateMessages = {
"/appearance/messages/alternateMessageBackground", false};
- IntSetting boldScale = {"/appearance/boldScale", 57};
+ FloatSetting boldScale = {"/appearance/boldScale", 50};
BoolSetting showTabCloseButton = {"/appearance/showTabCloseButton", true};
BoolSetting showTabLive = {"/appearance/showTabLiveButton", false};
BoolSetting hidePreferencesButton = {"/appearance/hidePreferencesButton",
@@ -67,7 +67,6 @@ public:
BoolSetting headerUptime = {"/appearance/splitheader/showUptime", false};
FloatSetting customThemeMultiplier = {"/appearance/customThemeMultiplier",
-0.5f};
- BoolSetting redDisabledMessages = {"/appearance/redStripes", true};
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
// false};
@@ -106,10 +105,7 @@ public:
/// Emotes
BoolSetting scaleEmotesByLineHeight = {"/emotes/scaleEmotesByLineHeight",
false};
- BoolSetting enableTwitchEmotes = {"/emotes/enableTwitchEmotes", true};
- BoolSetting enableBttvEmotes = {"/emotes/enableBTTVEmotes", true};
- BoolSetting enableFfzEmotes = {"/emotes/enableFFZEmotes", true};
- BoolSetting enableEmojis = {"/emotes/enableEmojis", true};
+ BoolSetting enableEmoteImages = {"/emotes/enableEmoteImages", true};
BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true};
FloatSetting emoteScale = {"/emotes/scale", 1.f};
@@ -128,6 +124,7 @@ public:
/// Ingored Users
BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers",
true};
+ IntSetting showIgnoredUsersMessages = {"/ignore/showIgnoredUsers", 0};
/// Moderation
QStringSetting timeoutAction = {"/moderation/timeoutAction", "Disable"};
@@ -147,7 +144,7 @@ public:
"/highlighting/whisperHighlight/enableSound", false};
BoolSetting enableWhisperHighlightTaskbar = {
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
- QStringSetting highlightColor = {"/highlighting/color", "#4B282C"};
+ QStringSetting highlightColor = {"/highlighting/color", ""};
BoolSetting longAlerts = {"/highlighting/alerts", false};
@@ -175,6 +172,8 @@ public:
"qrc:/sounds/ping3.wav"};
BoolSetting notificationToast = {"/notifications/enableToast", false};
+ IntSetting openFromToast = {"/notifications/openFromToast",
+ static_cast(ToastReaction::OpenInBrowser)};
/// External tools
// Streamlink
@@ -188,6 +187,9 @@ public:
/// Misc
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
QStringSetting currentVersion = {"/misc/currentVersion", ""};
+ BoolSetting loadTwitchMessageHistoryOnConnect = {
+ "/misc/twitch/loadMessageHistoryOnConnect", true};
+ IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0};
QStringSetting cachePath = {"/cache/path", ""};
diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp
index 70d70776c..b99b42a5d 100644
--- a/src/singletons/Theme.cpp
+++ b/src/singletons/Theme.cpp
@@ -27,9 +27,9 @@ void Theme::actuallyUpdate(double hue, double multiplier)
return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a);
};
- auto sat = qreal(0);
- auto isLight_ = this->isLightTheme();
- auto flat = isLight_;
+ const auto sat = qreal(0);
+ const auto isLight = this->isLightTheme();
+ const auto flat = isLight;
if (this->isLightTheme())
{
@@ -38,6 +38,9 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
+
+ // Highlighted Messages: theme support quick-fix
+ this->messages.backgrounds.highlighted = QColor("#BD8489");
}
else
{
@@ -46,13 +49,16 @@ void Theme::actuallyUpdate(double hue, double multiplier)
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
+
+ // Highlighted Messages: theme support quick-fix
+ this->messages.backgrounds.highlighted = QColor("#4B282C");
}
this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9);
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
this->splits.header.text = this->messages.textColors.regular;
this->splits.header.focusedText =
- isLight_ ? QColor("#198CFF") : QColor("#84C1FF");
+ isLight ? QColor("#198CFF") : QColor("#84C1FF");
this->splits.input.background = getColor(0, sat, flat ? 0.95 : 0.95);
this->splits.input.border = getColor(0, sat, flat ? 1 : 1);
@@ -62,20 +68,25 @@ void Theme::actuallyUpdate(double hue, double multiplier)
"border:" + this->tabs.selected.backgrounds.regular.color().name() +
";" + "color:" + this->messages.textColors.regular.name() + ";" + //
"selection-background-color:" +
- (isLight_ ? "#68B1FF"
- : this->tabs.selected.backgrounds.regular.color().name());
+ (isLight ? "#68B1FF"
+ : this->tabs.selected.backgrounds.regular.color().name());
this->splits.input.focusedLine = this->tabs.highlighted.line.regular;
this->splits.messageSeperator =
- isLight_ ? QColor(127, 127, 127) : QColor(60, 60, 60);
+ isLight ? QColor(127, 127, 127) : QColor(60, 60, 60);
this->splits.background = getColor(0, sat, 1);
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
// Highlighted Messages
- this->messages.backgrounds.highlighted =
- QColor(getSettings()->highlightColor);
+ // hidden setting from PR #744 - if set it will overwrite theme color
+ // TODO: implement full theme support
+ if (getSettings()->highlightColor != "")
+ {
+ this->messages.backgrounds.highlighted =
+ QColor(getSettings()->highlightColor);
+ }
}
void Theme::normalizeColor(QColor &color)
diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp
index 367724368..6b5ba9636 100644
--- a/src/singletons/Toasts.cpp
+++ b/src/singletons/Toasts.cpp
@@ -8,6 +8,8 @@
#include "providers/twitch/TwitchCommon.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Paths.hpp"
+#include "util/StreamLink.hpp"
+#include "widgets/helper/CommonTexts.hpp"
#ifdef Q_OS_WIN
@@ -25,13 +27,40 @@
namespace chatterino {
+std::map Toasts::reactionToString = {
+ {ToastReaction::OpenInBrowser, OPEN_IN_BROWSER},
+ {ToastReaction::OpenInPlayer, OPEN_PLAYER_IN_BROWSER},
+ {ToastReaction::OpenInStreamlink, OPEN_IN_STREAMLINK},
+ {ToastReaction::DontOpen, DONT_OPEN}};
+
bool Toasts::isEnabled()
{
#ifdef Q_OS_WIN
return WinToastLib::WinToast::isCompatible() &&
getSettings()->notificationToast;
-#endif
+#else
return false;
+#endif
+}
+
+QString Toasts::findStringFromReaction(const ToastReaction &reaction)
+{
+ auto iterator = Toasts::reactionToString.find(reaction);
+ if (iterator != Toasts::reactionToString.end())
+ {
+ return iterator->second;
+ }
+ else
+ {
+ return DONT_OPEN;
+ }
+}
+
+QString Toasts::findStringFromReaction(
+ const pajlada::Settings::Setting &value)
+{
+ int i = static_cast(value);
+ return Toasts::findStringFromReaction(static_cast(i));
}
void Toasts::sendChannelNotification(const QString &channelName, Platform p)
@@ -86,11 +115,33 @@ public:
void toastActivated() const
{
QString link;
- if (platform_ == Platform::Twitch)
+ auto toastReaction =
+ static_cast(getSettings()->openFromToast.getValue());
+
+ switch (toastReaction)
{
- link = "http://www.twitch.tv/" + channelName_;
+ case ToastReaction::OpenInBrowser:
+ if (platform_ == Platform::Twitch)
+ {
+ link = "http://www.twitch.tv/" + channelName_;
+ }
+ QDesktopServices::openUrl(QUrl(link));
+ break;
+ case ToastReaction::OpenInPlayer:
+ if (platform_ == Platform::Twitch)
+ {
+ link = "https://player.twitch.tv/?channel=" + channelName_;
+ }
+ QDesktopServices::openUrl(QUrl(link));
+ break;
+ case ToastReaction::OpenInStreamlink:
+ {
+ openStreamlinkForChannel(channelName_);
+ break;
+ }
+ // the fourth and last option is "don't open"
+ // in this case obviously nothing should happen
}
- QDesktopServices::openUrl(QUrl(link));
}
void toastActivated(int actionIndex) const
@@ -115,8 +166,17 @@ void Toasts::sendWindowsNotification(const QString &channelName, Platform p)
std::wstring widestr = std::wstring(utf8_text.begin(), utf8_text.end());
templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
- templ.setTextField(L"Click here to open in browser",
- WinToastLib::WinToastTemplate::SecondLine);
+ if (static_cast(getSettings()->openFromToast.getValue()) !=
+ ToastReaction::DontOpen)
+ {
+ QString mode =
+ Toasts::findStringFromReaction(getSettings()->openFromToast);
+ mode = mode.toLower();
+
+ templ.setTextField(L"Click here to " + mode.toStdWString(),
+ WinToastLib::WinToastTemplate::SecondLine);
+ }
+
QString Path;
if (p == Platform::Twitch)
{
diff --git a/src/singletons/Toasts.hpp b/src/singletons/Toasts.hpp
index 3a245346f..43c6d1da8 100644
--- a/src/singletons/Toasts.hpp
+++ b/src/singletons/Toasts.hpp
@@ -7,10 +7,21 @@ namespace chatterino {
enum class Platform : uint8_t;
+enum class ToastReaction {
+ OpenInBrowser = 0,
+ OpenInPlayer = 1,
+ OpenInStreamlink = 2,
+ DontOpen = 3
+};
+
class Toasts final : public Singleton
{
public:
void sendChannelNotification(const QString &channelName, Platform p);
+ static QString findStringFromReaction(const ToastReaction &reaction);
+ static QString findStringFromReaction(
+ const pajlada::Settings::Setting &reaction);
+ static std::map reactionToString;
static bool isEnabled();
@@ -18,6 +29,7 @@ private:
#ifdef Q_OS_WIN
void sendWindowsNotification(const QString &channelName, Platform p);
#endif
+
static void fetchChannelAvatar(
const QString channelName,
std::function successCallback);
diff --git a/src/singletons/TooltipPreviewImage.cpp b/src/singletons/TooltipPreviewImage.cpp
new file mode 100644
index 000000000..99cd9548b
--- /dev/null
+++ b/src/singletons/TooltipPreviewImage.cpp
@@ -0,0 +1,37 @@
+#include "TooltipPreviewImage.hpp"
+
+#include "Application.hpp"
+#include "singletons/WindowManager.hpp"
+#include "widgets/TooltipWidget.hpp"
+
+namespace chatterino {
+TooltipPreviewImage &TooltipPreviewImage::getInstance()
+{
+ static TooltipPreviewImage *instance = new TooltipPreviewImage();
+ return *instance;
+}
+
+TooltipPreviewImage::TooltipPreviewImage()
+{
+ connections_.push_back(getApp()->windows->gifRepaintRequested.connect([&] {
+ auto tooltipWidget = TooltipWidget::getInstance();
+ if (this->image_ && !tooltipWidget->isHidden())
+ {
+ auto pixmap = this->image_->pixmap();
+ if (pixmap)
+ {
+ tooltipWidget->setImage(*pixmap);
+ }
+ }
+ else
+ {
+ tooltipWidget->clearImage();
+ }
+ }));
+}
+
+void TooltipPreviewImage::setImage(ImagePtr image)
+{
+ this->image_ = image;
+}
+} // namespace chatterino
diff --git a/src/singletons/TooltipPreviewImage.hpp b/src/singletons/TooltipPreviewImage.hpp
new file mode 100644
index 000000000..67ba9cd95
--- /dev/null
+++ b/src/singletons/TooltipPreviewImage.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "messages/Image.hpp"
+
+namespace chatterino {
+class TooltipPreviewImage
+{
+public:
+ static TooltipPreviewImage &getInstance();
+ void setImage(ImagePtr image);
+
+ TooltipPreviewImage(const TooltipPreviewImage &) = delete;
+
+private:
+ TooltipPreviewImage();
+
+private:
+ ImagePtr image_ = nullptr;
+ std::vector connections_;
+};
+} // namespace chatterino
diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp
index ca797be17..59c84c7e3 100644
--- a/src/singletons/WindowManager.cpp
+++ b/src/singletons/WindowManager.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
@@ -75,10 +76,7 @@ WindowManager::WindowManager()
this->wordFlagsListener_.addSetting(settings->showBadgesSubscription);
this->wordFlagsListener_.addSetting(settings->showBadgesVanity);
this->wordFlagsListener_.addSetting(settings->showBadgesChatterino);
- this->wordFlagsListener_.addSetting(settings->enableBttvEmotes);
- this->wordFlagsListener_.addSetting(settings->enableEmojis);
- this->wordFlagsListener_.addSetting(settings->enableFfzEmotes);
- this->wordFlagsListener_.addSetting(settings->enableTwitchEmotes);
+ this->wordFlagsListener_.addSetting(settings->enableEmoteImages);
this->wordFlagsListener_.addSetting(settings->boldUsernames);
this->wordFlagsListener_.addSetting(settings->lowercaseDomains);
this->wordFlagsListener_.setCB([this] {
@@ -114,13 +112,12 @@ void WindowManager::updateWordTypeMask()
}
// emotes
- flags.set(settings->enableTwitchEmotes ? MEF::TwitchEmoteImage
- : MEF::TwitchEmoteText);
- flags.set(settings->enableFfzEmotes ? MEF::FfzEmoteImage
- : MEF::FfzEmoteText);
- flags.set(settings->enableBttvEmotes ? MEF::BttvEmoteImage
- : MEF::BttvEmoteText);
- flags.set(settings->enableEmojis ? MEF::EmojiImage : MEF::EmojiText);
+ if (settings->enableEmoteImages)
+ {
+ flags.set(MEF::EmoteImages);
+ }
+ flags.set(MEF::EmoteText);
+ flags.set(MEF::EmojiText);
// bits
flags.set(MEF::BitsAmount);
@@ -258,11 +255,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
// load file
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
- QFile file(settingsPath);
- file.open(QIODevice::ReadOnly);
- QByteArray data = file.readAll();
- QJsonDocument document = QJsonDocument::fromJson(data);
- QJsonArray windows_arr = document.object().value("windows").toArray();
+ QJsonArray windows_arr = this->loadWindowArray(settingsPath);
// "deserialize"
for (QJsonValue window_val : windows_arr)
@@ -390,10 +383,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
void WindowManager::save()
{
log("[WindowManager] Saving");
-
assertInGuiThread();
- auto app = getApp();
-
QJsonDocument document;
// "serialize"
@@ -477,7 +467,7 @@ void WindowManager::save()
// save file
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
- QFile file(settingsPath);
+ QSaveFile file(settingsPath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
QJsonDocument::JsonFormat format =
@@ -489,7 +479,7 @@ void WindowManager::save()
;
file.write(document.toJson(format));
- file.flush();
+ file.commit();
}
void WindowManager::sendAlert()
@@ -516,6 +506,7 @@ void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
case SplitNode::_Split:
{
obj.insert("type", "split");
+ obj.insert("moderationMode", node->getSplit()->getModerationMode());
QJsonObject split;
encodeChannel(node->getSplit()->getIndirectChannel(), split);
obj.insert("data", split);
@@ -621,4 +612,14 @@ void WindowManager::incGeneration()
this->generation_++;
}
+QJsonArray WindowManager::loadWindowArray(const QString &settingsPath)
+{
+ QFile file(settingsPath);
+ file.open(QIODevice::ReadOnly);
+ QByteArray data = file.readAll();
+ QJsonDocument document = QJsonDocument::fromJson(data);
+ QJsonArray windows_arr = document.object().value("windows").toArray();
+ return windows_arr;
+}
+
} // namespace chatterino
diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp
index 038eb41ab..dd9e7fb69 100644
--- a/src/singletons/WindowManager.hpp
+++ b/src/singletons/WindowManager.hpp
@@ -54,6 +54,7 @@ public:
virtual void initialize(Settings &settings, Paths &paths) override;
virtual void save() override;
void closeAll();
+ QJsonArray loadWindowArray(const QString &settingsPath);
int getGeneration() const;
void incGeneration();
diff --git a/src/singletons/helper/LoggingChannel.cpp b/src/singletons/helper/LoggingChannel.cpp
index 74b4ebe30..82230edcc 100644
--- a/src/singletons/helper/LoggingChannel.cpp
+++ b/src/singletons/helper/LoggingChannel.cpp
@@ -33,20 +33,9 @@ LoggingChannel::LoggingChannel(const QString &_channelName)
// FOURTF: change this when adding more providers
this->subDirectory = "Twitch/" + this->subDirectory;
- auto app = getApp();
-
getSettings()->logPath.connect([this](const QString &logPath, auto) {
- auto app = getApp();
-
- if (logPath.isEmpty())
- {
- this->baseDirectory = getPaths()->messageLogDirectory;
- }
- else
- {
- this->baseDirectory = logPath;
- }
-
+ this->baseDirectory =
+ logPath.isEmpty() ? getPaths()->messageLogDirectory : logPath;
this->openLogFile();
});
}
diff --git a/src/util/StreamLink.cpp b/src/util/StreamLink.cpp
index c213c83d8..4c3634c2f 100644
--- a/src/util/StreamLink.cpp
+++ b/src/util/StreamLink.cpp
@@ -36,8 +36,6 @@ namespace {
QString getStreamlinkProgram()
{
- auto app = getApp();
-
if (getSettings()->streamlinkUseCustomPath)
{
return getSettings()->streamlinkPath + "/" + getBinaryName();
@@ -66,7 +64,6 @@ namespace {
{
static QErrorMessage *msg = new QErrorMessage;
- auto app = getApp();
if (getSettings()->streamlinkUseCustomPath)
{
msg->showMessage(
@@ -172,8 +169,6 @@ void getStreamQualities(const QString &channelURL,
void openStreamlink(const QString &channelURL, const QString &quality,
QStringList extraArguments)
{
- auto app = getApp();
-
QStringList arguments;
QString additionalOptions = getSettings()->streamlinkOpts.getValue();
@@ -202,8 +197,6 @@ void openStreamlink(const QString &channelURL, const QString &quality,
void openStreamlinkForChannel(const QString &channel)
{
- auto app = getApp();
-
QString channelURL = "twitch.tv/" + channel;
QString preferredQuality = getSettings()->preferredQuality;
diff --git a/src/widgets/AccountSwitchPopupWidget.cpp b/src/widgets/AccountSwitchPopupWidget.cpp
index 6e9cfcebe..693fe6da0 100644
--- a/src/widgets/AccountSwitchPopupWidget.cpp
+++ b/src/widgets/AccountSwitchPopupWidget.cpp
@@ -14,6 +14,9 @@ AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
: QWidget(parent)
{
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
+#ifdef Q_OS_LINUX
+ this->setWindowFlag(Qt::Popup);
+#endif
this->setContentsMargins(0, 0, 0, 0);
diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp
index 88a165afb..416834f70 100644
--- a/src/widgets/Notebook.cpp
+++ b/src/widgets/Notebook.cpp
@@ -6,11 +6,11 @@
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/InitUpdateButton.hpp"
+#include "util/Shortcut.hpp"
#include "widgets/Window.hpp"
#include "widgets/dialogs/SettingsDialog.hpp"
#include "widgets/helper/NotebookButton.hpp"
#include "widgets/helper/NotebookTab.hpp"
-#include "util/Shortcut.hpp"
#include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp"
diff --git a/src/widgets/Scrollbar.cpp b/src/widgets/Scrollbar.cpp
index a202dcd5c..4c7efeede 100644
--- a/src/widgets/Scrollbar.cpp
+++ b/src/widgets/Scrollbar.cpp
@@ -110,7 +110,7 @@ void Scrollbar::setSmallChange(qreal value)
void Scrollbar::setDesiredValue(qreal value, bool animated)
{
- animated &= getSettings()->enableSmoothScrolling.getValue();
+ animated &= getSettings()->enableSmoothScrolling;
value = std::max(this->minimum_,
std::min(this->maximum_ - this->largeChange_, value));
diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp
index fb5551522..11741e015 100644
--- a/src/widgets/Window.cpp
+++ b/src/widgets/Window.cpp
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
namespace chatterino {
@@ -43,6 +44,10 @@ Window::Window(WindowType type)
this->addShortcuts();
this->addLayout();
+#ifdef Q_OS_MACOS
+ this->addMenuBar();
+#endif
+
this->signalHolder_.managedConnect(
getApp()->accounts->twitch.currentUserChanged,
[this] { this->onAccountSelected(); });
@@ -336,6 +341,18 @@ void Window::addShortcuts()
});
}
+void Window::addMenuBar()
+{
+ QMenuBar *mainMenu = new QMenuBar();
+ mainMenu->setNativeMenuBar(true);
+
+ QMenu *menu = new QMenu(QString());
+ mainMenu->addMenu(menu);
+ QAction *prefs = menu->addAction(QString());
+ prefs->setMenuRole(QAction::PreferencesRole);
+ connect(prefs, &QAction::triggered, this, [] { SettingsDialog::showDialog(); });
+}
+
#define UGLYMACROHACK1(s) #s
#define UGLYMACROHACK(s) UGLYMACROHACK1(s)
diff --git a/src/widgets/Window.hpp b/src/widgets/Window.hpp
index e27bdfae9..d2c1c1922 100644
--- a/src/widgets/Window.hpp
+++ b/src/widgets/Window.hpp
@@ -38,6 +38,7 @@ private:
void addShortcuts();
void addLayout();
void onAccountSelected();
+ void addMenuBar();
WindowType type_;
diff --git a/src/widgets/dialogs/SelectChannelDialog.cpp b/src/widgets/dialogs/SelectChannelDialog.cpp
index 2d8218e69..f965ccb5c 100644
--- a/src/widgets/dialogs/SelectChannelDialog.cpp
+++ b/src/widgets/dialogs/SelectChannelDialog.cpp
@@ -319,7 +319,6 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
{
return false;
}
- return true;
}
else if (event->type() == QEvent::KeyRelease)
{
diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp
index 10ad52fc8..f326d9cc9 100644
--- a/src/widgets/dialogs/UserInfoPopup.cpp
+++ b/src/widgets/dialogs/UserInfoPopup.cpp
@@ -84,6 +84,8 @@ UserInfoPopup::UserInfoPopup()
.assign(&this->ui_.ignoreHighlights);
auto viewLogs = user.emplace(this);
viewLogs->getLabel().setText("Online logs");
+ auto usercard = user.emplace(this);
+ usercard->getLabel().setText("Usercard");
auto mod = user.emplace