mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Merge pull request #1133 from Chatterino/nightly
Merge Nightly into Master
This commit is contained in:
commit
6662053061
108 changed files with 2549 additions and 1166 deletions
3
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
3
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Description
|
||||
|
||||
<!-- If applicable, please include a summary of what you've changed and what issue is fixed. In the case of a bug fix, please include steps to reproduce the bug so the pull request can be tested -->
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -17,4 +17,4 @@
|
|||
|
||||
[submodule "lib/appbase"]
|
||||
path = lib/appbase
|
||||
url = https://github.com/fourtf/appbase
|
||||
url = https://github.com/Chatterino/appbase
|
||||
|
|
41
.travis.yml
41
.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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -103,6 +103,13 @@ 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. The `releases` directory will now be populated with all the required files to make the chatterino build standalone.
|
||||
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
|
||||
|
||||
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)).
|
||||
|
|
37
CMakeLists.txt
Normal file
37
CMakeLists.txt
Normal file
|
@ -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()
|
|
@ -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.
|
||||
|
||||
|
|
65
appveyor.yml
Normal file
65
appveyor.yml
Normal file
|
@ -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
|
243
chatterino.pro
243
chatterino.pro
|
@ -1,12 +1,3 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# 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
|
||||
|
@ -34,7 +25,7 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
|||
}
|
||||
|
||||
# Icons
|
||||
#macx:ICON = resources/images/chatterino2.icns
|
||||
macx:ICON = resources/chatterino.icns
|
||||
win32:RC_FILE = resources/windows.rc
|
||||
|
||||
macx {
|
||||
|
@ -59,31 +50,43 @@ include(lib/wintoast.pri)
|
|||
|
||||
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/HighlightBlacklistModel.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 \
|
||||
|
@ -91,30 +94,57 @@ SOURCES += \
|
|||
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/controllers/moderationactions/ModerationAction.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 \
|
||||
|
@ -129,6 +159,7 @@ SOURCES += \
|
|||
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 \
|
||||
|
@ -145,17 +176,23 @@ SOURCES += \
|
|||
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 \
|
||||
|
@ -163,57 +200,21 @@ SOURCES += \
|
|||
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
|
||||
src/controllers/pings/PingController.cpp \
|
||||
src/controllers/pings/PingModel.cpp \
|
||||
|
||||
HEADERS += \
|
||||
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/Atomic.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 \
|
||||
|
@ -224,7 +225,10 @@ HEADERS += \
|
|||
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 \
|
||||
|
@ -232,20 +236,26 @@ HEADERS += \
|
|||
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/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/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 \
|
||||
|
@ -255,37 +265,68 @@ HEADERS += \
|
|||
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/controllers/moderationactions/ModerationAction.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 \
|
||||
|
@ -303,10 +344,12 @@ HEADERS += \
|
|||
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 \
|
||||
|
@ -320,17 +363,23 @@ HEADERS += \
|
|||
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 \
|
||||
|
@ -338,56 +387,8 @@ HEADERS += \
|
|||
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
|
||||
src/controllers/pings/PingController.hpp \
|
||||
src/controllers/pings/PingModel.hpp \
|
||||
|
||||
RESOURCES += \
|
||||
resources/resources.qrc \
|
||||
|
@ -401,3 +402,23 @@ FORMS +=
|
|||
#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
|
||||
}
|
||||
|
|
20
docs/ENV.md
Normal file
20
docs/ENV.md
Normal file
|
@ -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
|
|
@ -1 +1 @@
|
|||
Subproject commit e6a31d5228ed8969596d6fdbd030f71a7a17f30d
|
||||
Subproject commit d054925734cf26576346a1da856f7ab0d4b6c0a5
|
1
resources/.gitignore
vendored
Normal file
1
resources/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
linuxinstall
|
BIN
resources/buttons/trashCan.png
Normal file
BIN
resources/buttons/trashCan.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 687 B |
124
resources/buttons/trashcan.svg
Normal file
124
resources/buttons/trashcan.svg
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32mm"
|
||||
height="32mm"
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:export-filename="/home/pajlada/git/chatterino2/resources/buttons/trashcan2.png"
|
||||
inkscape:export-xdpi="50.799999"
|
||||
inkscape:export-ydpi="50.799999"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="trashcan.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4536"
|
||||
osb:paint="gradient">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4532" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop4534" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="58.051498"
|
||||
inkscape:cy="84.215087"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1529"
|
||||
inkscape:window-height="1419"
|
||||
inkscape:window-x="2160"
|
||||
inkscape:window-y="2400"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4818"
|
||||
originx="-74.790005"
|
||||
originy="-199.8473"
|
||||
units="mm"
|
||||
spacingx="1"
|
||||
spacingy="1" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<cc:license
|
||||
rdf:resource="" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-74.789996,-65.152683)">
|
||||
<path
|
||||
style="fill:none;stroke:#898395;stroke-width:3.5999999;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 82.789996,69.152684 v 25 h 16 v -25"
|
||||
id="path4820"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#898395;stroke-width:3.6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 102.79,69.152684 H 78.789994"
|
||||
id="path4826"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc">
|
||||
<title
|
||||
id="title4970">Trashcan top</title>
|
||||
</path>
|
||||
<path
|
||||
style="fill:none;stroke:#898395;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 87.789996,74.999984 v 14"
|
||||
id="path4830"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#898395;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 93.789996,88.999984 v -14"
|
||||
id="path4832"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<circle
|
||||
style="fill:#898395;fill-opacity:1;stroke:none;stroke-width:6.75056219;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="path4974"
|
||||
cx="90.789993"
|
||||
cy="67.069061"
|
||||
r="1.75" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="translate(-74.789996,-65.152683)" />
|
||||
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
9
resources/chatterino.desktop
Normal file
9
resources/chatterino.desktop
Normal file
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<RCC>
|
||||
<qresource prefix="/"> <file>chatterino2.icns</file>
|
||||
<qresource prefix="/"> <file>chatterino.icns</file>
|
||||
<file>contributors.txt</file>
|
||||
<file>emoji.json</file>
|
||||
<file>emojidata.txt</file>
|
||||
|
@ -24,6 +24,7 @@
|
|||
<file>buttons/modModeEnabled.png</file>
|
||||
<file>buttons/modModeEnabled2.png</file>
|
||||
<file>buttons/timeout.png</file>
|
||||
<file>buttons/trashCan.png</file>
|
||||
<file>buttons/unban.png</file>
|
||||
<file>buttons/unmod.png</file>
|
||||
<file>buttons/update.png</file>
|
||||
|
|
|
@ -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<CommandController>())
|
||||
, highlights(&this->emplace<HighlightController>())
|
||||
, notifications(&this->emplace<NotificationController>())
|
||||
, pings(&this->emplace<PingController>())
|
||||
, ignores(&this->emplace<IgnoreController>())
|
||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
||||
, moderationActions(&this->emplace<ModerationActions>())
|
||||
|
@ -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();
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
QPixmap modModeEnabled;
|
||||
QPixmap modModeEnabled2;
|
||||
QPixmap timeout;
|
||||
QPixmap trashCan;
|
||||
QPixmap unban;
|
||||
QPixmap unmod;
|
||||
QPixmap update;
|
||||
|
|
|
@ -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<MessagePtr> 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;
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace chatterino {
|
|||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
enum class MessageFlag : uint16_t;
|
||||
enum class MessageFlag : uint32_t;
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
class Channel : public std::enable_shared_from_this<Channel>
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
40
src/common/Env.cpp
Normal file
40
src/common/Env.cpp
Normal file
|
@ -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
|
19
src/common/Env.hpp
Normal file
19
src/common/Env.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Env
|
||||
{
|
||||
Env();
|
||||
|
||||
public:
|
||||
static const Env &get();
|
||||
|
||||
const QString recentMessagesApiUrl;
|
||||
const QString linkResolverUrl;
|
||||
const QString twitchEmoteSetResolverUrl;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
#include <unordered_map>
|
||||
|
||||
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<Prefix>;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
|
@ -30,9 +34,18 @@ struct hash<chatterino::Prefix> {
|
|||
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<QString> items;
|
||||
std::set<QString, CaseInsensitiveLess> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
|
|
|
@ -31,12 +31,12 @@ int AccountModel::beforeInsert(const std::shared_ptr<Account> &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;
|
||||
}
|
||||
|
|
|
@ -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<TimestampElement>();
|
||||
b.emplace<TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
|
||||
MessageElementFlag::Text, MessageColor::Text,
|
||||
FontStyle::ChatMediumBold);
|
||||
b.emplace<TextElement>("->", MessageElementFlag::Text,
|
||||
getApp()->themes->messages.textColors.system);
|
||||
b.emplace<TextElement>(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<EmotePtr>{};
|
||||
for (int i = 2; i < words.length(); i++)
|
||||
{
|
||||
{ // twitch emote
|
||||
auto it = accemotes.emotes.find({words[i]});
|
||||
if (it != accemotes.emotes.end())
|
||||
{
|
||||
b.emplace<EmoteElement>(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<EmoteElement>(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<EmoteElement>(emote,
|
||||
MessageElementFlag::EmojiAll);
|
||||
}
|
||||
void operator()(const QString &string,
|
||||
MessageBuilder &b) const
|
||||
{
|
||||
auto linkString = b.matchLink(string);
|
||||
if (linkString.isEmpty())
|
||||
{
|
||||
b.emplace<TextElement>(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<MessageFlags>(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<TimestampElement>();
|
||||
b.emplace<TextElement>(
|
||||
app->accounts->twitch.getCurrent()->getUserName(),
|
||||
MessageElementFlag::Text, MessageColor::Text,
|
||||
FontStyle::ChatMediumBold);
|
||||
b.emplace<TextElement>("->", MessageElementFlag::Text);
|
||||
b.emplace<TextElement>(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<EmotePtr>{};
|
||||
for (int i = 2; i < words.length(); i++)
|
||||
{
|
||||
{ // twitch emote
|
||||
auto it = accemotes.emotes.find({words[i]});
|
||||
if (it != accemotes.emotes.end())
|
||||
{
|
||||
b.emplace<EmoteElement>(
|
||||
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<EmoteElement>(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<EmoteElement>(
|
||||
emote, MessageElementFlag::EmojiAll);
|
||||
}
|
||||
void operator()(const QString &string,
|
||||
MessageBuilder &b) const
|
||||
{
|
||||
b.emplace<TextElement>(
|
||||
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<MessageFlags>(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()
|
||||
|
|
|
@ -49,7 +49,8 @@ private:
|
|||
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
||||
commandsSetting_;
|
||||
|
||||
QString execCustomCommand(const QStringList &words, const Command &command);
|
||||
QString execCustomCommand(const QStringList &words, const Command &command,
|
||||
bool dryRun);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -12,6 +12,8 @@ class Paths;
|
|||
|
||||
class IgnoreModel;
|
||||
|
||||
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
|
||||
|
||||
class IgnoreController final : public Singleton
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
70
src/controllers/pings/PingController.cpp
Normal file
70
src/controllers/pings/PingController.cpp
Normal file
|
@ -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<int>::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
|
36
src/controllers/pings/PingController.hpp
Normal file
36
src/controllers/pings/PingController.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#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<QString> channelVector;
|
||||
|
||||
ChatterinoSetting<std::vector<QString>> pingSetting_ = {"/pings/muted"};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
28
src/controllers/pings/PingModel.cpp
Normal file
28
src/controllers/pings/PingModel.cpp
Normal file
|
@ -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<QString>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
QString PingModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const QString &original)
|
||||
{
|
||||
return QString(row[0]->data(Qt::DisplayRole).toString());
|
||||
}
|
||||
|
||||
// turn a model
|
||||
void PingModel::getRowFromItem(const QString &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
28
src/controllers/pings/PingModel.hpp
Normal file
28
src/controllers/pings/PingModel.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class PingController;
|
||||
|
||||
class PingModel : public SignalVectorModel<QString>
|
||||
{
|
||||
explicit PingModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual QString getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const QString &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const QString &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
friend class PingController;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,10 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum HistoricMessageAppearance {
|
||||
Crossed = (1 << 0),
|
||||
Greyed = (1 << 1),
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -28,8 +28,8 @@ template <typename T>
|
|||
class LimitedQueue
|
||||
{
|
||||
protected:
|
||||
typedef std::shared_ptr<std::vector<T>> Chunk;
|
||||
typedef std::shared_ptr<std::vector<Chunk>> ChunkVector;
|
||||
using Chunk = std::vector<T>;
|
||||
using ChunkVector = std::vector<std::shared_ptr<Chunk>>;
|
||||
|
||||
public:
|
||||
LimitedQueue(size_t limit = 1000)
|
||||
|
@ -42,9 +42,8 @@ public:
|
|||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
this->chunks_ =
|
||||
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||
Chunk chunk = std::make_shared<std::vector<T>>();
|
||||
this->chunks_ = std::make_shared<ChunkVector>();
|
||||
auto chunk = std::make_shared<Chunk>();
|
||||
chunk->resize(this->chunkSize_);
|
||||
this->chunks_->push_back(chunk);
|
||||
this->firstChunkOffset_ = 0;
|
||||
|
@ -57,23 +56,21 @@ public:
|
|||
{
|
||||
std::lock_guard<std::mutex> 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<std::shared_ptr<std::vector<T>>>>();
|
||||
// Last chunk is full, create a new one and rebuild our chunk vector
|
||||
auto newVector = std::make_shared<ChunkVector>();
|
||||
|
||||
// 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<std::vector<T>>();
|
||||
auto newChunk = std::make_shared<Chunk>();
|
||||
newChunk->resize(this->chunkSize_);
|
||||
newVector->push_back(newChunk);
|
||||
|
||||
|
@ -98,8 +95,7 @@ public:
|
|||
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||
|
||||
// create new vector to clone chunks into
|
||||
ChunkVector newChunks = std::make_shared<
|
||||
std::vector<std::shared_ptr<std::vector<T>>>>();
|
||||
auto newChunks = std::make_shared<ChunkVector>();
|
||||
|
||||
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<qsizetype>(items.size()));
|
||||
Chunk newFirstChunk = std::make_shared<std::vector<T>>();
|
||||
auto newFirstChunk = std::make_shared<Chunk>();
|
||||
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<std::vector<T>>();
|
||||
auto newChunk = std::make_shared<Chunk>();
|
||||
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<std::vector<T>>();
|
||||
auto newChunk = std::make_shared<Chunk>();
|
||||
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<std::shared_ptr<std::vector<T>>>>();
|
||||
auto newVector = std::make_shared<ChunkVector>();
|
||||
|
||||
// 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<ChunkVector> chunks_;
|
||||
std::mutex mutex_;
|
||||
|
||||
size_t firstChunkOffset_;
|
||||
size_t lastChunkEnd_;
|
||||
size_t limit_;
|
||||
const size_t limit_;
|
||||
|
||||
const size_t chunkSize_ = 100;
|
||||
};
|
||||
|
|
|
@ -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<MessageFlag>;
|
||||
|
||||
|
@ -47,6 +48,7 @@ struct Message : boost::noncopyable {
|
|||
QTime parseTime;
|
||||
QString id;
|
||||
QString searchText;
|
||||
QString messageText;
|
||||
QString loginName;
|
||||
QString displayName;
|
||||
QString localizedName;
|
||||
|
|
|
@ -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<MessagePtr, MessagePtr> makeAutomodMessage(
|
|||
|
||||
builder = MessageBuilder();
|
||||
builder.emplace<TimestampElement>();
|
||||
builder.emplace<TwitchModerationElement>();
|
||||
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<TimestampElement>();
|
||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||
MessageColor::System);
|
||||
this->message().searchText = text;
|
||||
}
|
||||
|
||||
MessageBuilder::MessageBuilder(SystemMessageTag, const QString &text)
|
||||
: MessageBuilder()
|
||||
{
|
||||
this->emplace<TimestampElement>();
|
||||
this->emplace<TextElement>(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<TextElement>(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<TimestampElement>();
|
||||
this->emplace<TextElement>(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<TextElement>(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<TextElement>(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<TextElement>(lowercaseLinkString,
|
||||
MessageElementFlag::LowercaseLink, textColor)
|
||||
->setLink(linkElement);
|
||||
auto linkMEOriginal =
|
||||
this->emplace<TextElement>(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
|
||||
|
|
|
@ -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<MessageElement> element);
|
||||
QString matchLink(const QString &string);
|
||||
void addLink(const QString &origLink, const QString &matchedLink);
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T *emplace(Args &&... args)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class QPainter;
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
enum class MessageFlag : uint16_t;
|
||||
enum class MessageFlag : uint32_t;
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
struct Margin {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<MessageElementFlag> 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
|
||||
|
|
|
@ -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<void(QString, Link)> 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 {
|
||||
|
|
|
@ -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<EmojiData> &emoji) {
|
||||
|
|
|
@ -231,9 +231,8 @@ void AbstractIrcServer::onConnected()
|
|||
{
|
||||
std::lock_guard<std::mutex> 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<Channel> &weak : this->channels.values())
|
||||
{
|
||||
|
@ -279,7 +278,7 @@ void AbstractIrcServer::onDisconnected()
|
|||
continue;
|
||||
}
|
||||
|
||||
chan->addMessage(disconnected);
|
||||
chan->addMessage(disconnectedMsg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,69 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
static QMap<QString, QString> parseBadges(QString badgesString)
|
||||
{
|
||||
QMap<QString, QString> 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<MessagePtr> IrcMessageHandler::parseMessage(
|
||||
Channel *channel, Communi::IrcMessage *message)
|
||||
{
|
||||
std::vector<MessagePtr> builtMessages;
|
||||
|
||||
auto command = message->command();
|
||||
|
||||
if (command == "PRIVMSG")
|
||||
{
|
||||
return this->parsePrivMessage(
|
||||
channel, static_cast<Communi::IrcPrivateMessage *>(message));
|
||||
}
|
||||
else if (command == "USERNOTICE")
|
||||
{
|
||||
return this->parseUserNoticeMessage(channel, message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
return this->parseNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
|
||||
return builtMessages;
|
||||
}
|
||||
|
||||
std::vector<MessagePtr> IrcMessageHandler::parsePrivMessage(
|
||||
Channel *channel, Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
std::vector<MessagePtr> 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<TwitchChannel *>(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<TwitchChannel *>(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<MessagePtr> IrcMessageHandler::parseUserNoticeMessage(
|
||||
Channel *channel, Communi::IrcMessage *message)
|
||||
{
|
||||
std::vector<MessagePtr> 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<MessagePtr> IrcMessageHandler::parseNoticeMessage(
|
||||
Communi::IrcNoticeMessage *message)
|
||||
{
|
||||
std::vector<MessagePtr> 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 <msg-id>\" - 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(
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <IrcMessage>
|
||||
#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<MessagePtr> parseMessage(Channel *channel,
|
||||
Communi::IrcMessage *message);
|
||||
|
||||
// parsePrivMessage arses a single IRC PRIVMSG into 0-1 Chatterino messages
|
||||
std::vector<MessagePtr> 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<MessagePtr> 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<MessagePtr> parseNoticeMessage(
|
||||
Communi::IrcNoticeMessage *message);
|
||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleJoinMessage(Communi::IrcMessage *message);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <QThread>
|
||||
|
||||
#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> 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 {
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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<MessagePtr> messages;
|
||||
std::vector<Communi::IrcMessage *> 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<Communi::IrcPrivateMessage *>(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<Outcome, UsernameSet> 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<MessagePtr> 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<int>(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();
|
||||
|
|
|
@ -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<QString> roomID_;
|
||||
|
||||
UniqueAccess<QStringList> joinedUsers_;
|
||||
|
@ -159,7 +166,6 @@ private:
|
|||
bool partedUsersMergeQueued_ = false;
|
||||
|
||||
// --
|
||||
QByteArray messageSuffix_;
|
||||
QString lastSentMessage_;
|
||||
QObject lifetimeGuard_;
|
||||
QTimer liveStatusTimer_;
|
||||
|
|
|
@ -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<ShowIgnoredUsersMessages>(
|
||||
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<TimestampElement>(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<TextElement>(lowercaseLinkString,
|
||||
MessageElementFlag::LowercaseLink,
|
||||
textColor)
|
||||
->setLink(link);
|
||||
auto linkMEOriginal =
|
||||
this->emplace<TextElement>(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<TextElement>(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<EmotePtr>{};
|
||||
{ // bttv/ffz emote
|
||||
if ((emote = bttvemotes.emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::BttvEmote;
|
||||
}
|
||||
else if ((emote = ffzemotes.emote(name)))
|
||||
{
|
||||
flags = MessageElementFlag::FfzEmote;
|
||||
}
|
||||
if (emote)
|
||||
{
|
||||
this->emplace<EmoteElement>(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<EmotePtr>{};
|
||||
|
||||
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<EmoteElement>(
|
||||
badge.get(), MessageElementFlag::BadgeVanity)
|
||||
this->emplace<BadgeElement>(
|
||||
_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<EmoteElement>(badge.get(),
|
||||
this->emplace<BadgeElement>(_badge.get(),
|
||||
MessageElementFlag::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
}
|
||||
|
@ -1112,7 +1071,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
{
|
||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||
{
|
||||
this->emplace<EmoteElement>(
|
||||
this->emplace<BadgeElement>(
|
||||
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<EmoteElement>(
|
||||
this->emplace<BadgeElement>(
|
||||
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<EmoteElement>(badgeEmote.get(),
|
||||
this->emplace<BadgeElement>(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<EmoteElement>(badge.get(),
|
||||
this->emplace<BadgeElement>(_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<EmoteElement>(*chatterinoBadgePtr,
|
||||
this->emplace<BadgeElement>(*chatterinoBadgePtr,
|
||||
MessageElementFlag::BadgeChatterino);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<std::tuple<int, EmotePtr, EmoteName>> &vec, std::vector<int> &correctPositions);
|
||||
void appendTwitchEmote(
|
||||
const QString &emote,
|
||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,
|
||||
std::vector<int> &correctPositions);
|
||||
Outcome tryAppendEmote(const EmoteName &name);
|
||||
|
||||
void addWords(
|
||||
|
|
|
@ -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<Channel> TwitchServer::getCustomChannel(
|
|||
{
|
||||
static auto channel =
|
||||
std::make_shared<Channel>("$$$", 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<Channel> 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<std::mutex> guard(this->lastMessageMutex_);
|
||||
|
||||
// std::queue<std::chrono::steady_clock::time_point>
|
||||
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();
|
||||
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
#include "messages/HistoricMessageAppearance.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include <pajlada/settings/settinglistener.hpp>
|
||||
|
@ -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<int>(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", ""};
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<ToastReaction, QString> 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<int> &value)
|
||||
{
|
||||
int i = static_cast<int>(value);
|
||||
return Toasts::findStringFromReaction(static_cast<ToastReaction>(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<ToastReaction>(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<ToastReaction>(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)
|
||||
{
|
||||
|
|
|
@ -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<int> &reaction);
|
||||
static std::map<ToastReaction, QString> 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<void(QString)> successCallback);
|
||||
|
|
37
src/singletons/TooltipPreviewImage.cpp
Normal file
37
src/singletons/TooltipPreviewImage.cpp
Normal file
|
@ -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
|
21
src/singletons/TooltipPreviewImage.hpp
Normal file
21
src/singletons/TooltipPreviewImage.hpp
Normal file
|
@ -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<pajlada::Signals::ScopedConnection> connections_;
|
||||
};
|
||||
} // namespace chatterino
|
|
@ -22,6 +22,7 @@
|
|||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <QShortcut>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <QMenuBar>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ private:
|
|||
void addShortcuts();
|
||||
void addLayout();
|
||||
void onAccountSelected();
|
||||
void addMenuBar();
|
||||
|
||||
WindowType type_;
|
||||
|
||||
|
|
|
@ -319,7 +319,6 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
|
|||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (event->type() == QEvent::KeyRelease)
|
||||
{
|
||||
|
|
|
@ -84,6 +84,8 @@ UserInfoPopup::UserInfoPopup()
|
|||
.assign(&this->ui_.ignoreHighlights);
|
||||
auto viewLogs = user.emplace<EffectLabel2>(this);
|
||||
viewLogs->getLabel().setText("Online logs");
|
||||
auto usercard = user.emplace<EffectLabel2>(this);
|
||||
usercard->getLabel().setText("Usercard");
|
||||
|
||||
auto mod = user.emplace<Button>(this);
|
||||
mod->setPixmap(app->resources->buttons.mod);
|
||||
|
@ -103,6 +105,12 @@ UserInfoPopup::UserInfoPopup()
|
|||
logs->show();
|
||||
});
|
||||
|
||||
QObject::connect(usercard.getElement(), &Button::leftClicked, [this] {
|
||||
QDesktopServices::openUrl("https://www.twitch.tv/popout/" +
|
||||
this->channel_->getName() +
|
||||
"/viewercard/" + this->userName_);
|
||||
});
|
||||
|
||||
QObject::connect(mod.getElement(), &Button::leftClicked, [this] {
|
||||
this->channel_->sendMessage("/mod " + this->userName_);
|
||||
});
|
||||
|
@ -128,8 +136,8 @@ UserInfoPopup::UserInfoPopup()
|
|||
this->userName_, Qt::CaseInsensitive) == 0;
|
||||
|
||||
visibilityMod = twitchChannel->isBroadcaster() && !isMyself;
|
||||
visibilityUnmod = visibilityMod ||
|
||||
(twitchChannel->isMod() && isMyself);
|
||||
visibilityUnmod =
|
||||
visibilityMod || (twitchChannel->isMod() && isMyself);
|
||||
}
|
||||
mod->setVisible(visibilityMod);
|
||||
unmod->setVisible(visibilityUnmod);
|
||||
|
@ -147,8 +155,8 @@ UserInfoPopup::UserInfoPopup()
|
|||
TwitchChannel *twitchChannel =
|
||||
dynamic_cast<TwitchChannel *>(this->channel_.get());
|
||||
|
||||
bool hasModRights = twitchChannel ? twitchChannel->hasModRights()
|
||||
: false;
|
||||
bool hasModRights =
|
||||
twitchChannel ? twitchChannel->hasModRights() : false;
|
||||
lineMod->setVisible(hasModRights);
|
||||
timeout->setVisible(hasModRights);
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/TooltipPreviewImage.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/DistanceBetweenPoints.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
|
@ -294,8 +295,12 @@ void ChannelView::scaleChangedEvent(float scale)
|
|||
|
||||
if (this->goToBottom_)
|
||||
{
|
||||
auto factor = this->qtFontScale();
|
||||
#ifdef Q_OS_MACOS
|
||||
factor = scale * 80.f / this->logicalDpiX() * this->devicePixelRatioF();
|
||||
#endif
|
||||
this->goToBottom_->getLabel().setFont(
|
||||
getFonts()->getFont(FontStyle::UiMedium, this->qtFontScale()));
|
||||
getFonts()->getFont(FontStyle::UiMedium, factor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,6 +312,7 @@ void ChannelView::queueUpdate()
|
|||
// }
|
||||
|
||||
// this->repaint();
|
||||
|
||||
this->update();
|
||||
|
||||
// this->updateTimer.start();
|
||||
|
@ -1213,6 +1219,28 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||
}
|
||||
else
|
||||
{
|
||||
auto &tooltipPreviewImage = TooltipPreviewImage::getInstance();
|
||||
auto emoteElement = dynamic_cast<const EmoteElement *>(
|
||||
&hoverLayoutElement->getCreator());
|
||||
|
||||
if (emoteElement && getSettings()->emotesTooltipPreview.getValue())
|
||||
{
|
||||
if (event->modifiers() == Qt::ShiftModifier ||
|
||||
getSettings()->emotesTooltipPreview.getValue() == 1)
|
||||
{
|
||||
tooltipPreviewImage.setImage(
|
||||
emoteElement->getEmote()->images.getImage(3.0));
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltipPreviewImage.setImage(nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tooltipPreviewImage.setImage(nullptr);
|
||||
}
|
||||
|
||||
tooltipWidget->moveTo(this, event->globalPos());
|
||||
tooltipWidget->setWordWrap(isLinkValid);
|
||||
tooltipWidget->setText(tooltip);
|
||||
|
@ -1667,7 +1695,12 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
|||
case Link::UserAction:
|
||||
{
|
||||
QString value = link.value;
|
||||
value.replace("{user}", layout->getMessage()->loginName);
|
||||
|
||||
value.replace("{user}", layout->getMessage()->loginName)
|
||||
.replace("{channel}", this->channel_->getName())
|
||||
.replace("{msg-id}", layout->getMessage()->id)
|
||||
.replace("{message}", layout->getMessage()->messageText);
|
||||
|
||||
this->channel_->sendMessage(value);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "messages/LimitedQueue.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Selection.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
#include <QPaintEvent>
|
||||
|
@ -25,7 +26,7 @@ using ChannelPtr = std::shared_ptr<Channel>;
|
|||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class MessageFlag : uint16_t;
|
||||
enum class MessageFlag : uint32_t;
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
class MessageLayout;
|
||||
|
|
6
src/widgets/helper/CommonTexts.hpp
Normal file
6
src/widgets/helper/CommonTexts.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#define OPEN_IN_BROWSER "Open in browser"
|
||||
#define OPEN_PLAYER_IN_BROWSER "Open in player in browser"
|
||||
#define OPEN_IN_STREAMLINK "Open in streamlink"
|
||||
#define DONT_OPEN "Don't open"
|
|
@ -29,8 +29,6 @@ NotebookTab::NotebookTab(Notebook *notebook)
|
|||
, notebook_(notebook)
|
||||
, menu_(this)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
this->setAcceptDrops(true);
|
||||
|
||||
this->positionChangedAnimation_.setEasingCurve(
|
||||
|
@ -527,8 +525,6 @@ void NotebookTab::dragEnterEvent(QDragEnterEvent *event)
|
|||
|
||||
void NotebookTab::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
if (getSettings()->showTabCloseButton &&
|
||||
this->notebook_->getAllowUserTabManagement()) //
|
||||
{
|
||||
|
|
|
@ -98,7 +98,7 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
|
|||
QString currentCompletionPrefix = this->textUnderCursor();
|
||||
|
||||
// check if there is something to complete
|
||||
if (!currentCompletionPrefix.size())
|
||||
if (currentCompletionPrefix.size() <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -228,6 +228,27 @@ void ResizingTextEdit::insertCompletion(const QString &completion)
|
|||
this->setTextCursor(tc);
|
||||
}
|
||||
|
||||
bool ResizingTextEdit::canInsertFromMimeData(const QMimeData *source) const
|
||||
{
|
||||
if (source->hasImage())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (source->hasFormat("text/plain"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ResizingTextEdit::insertFromMimeData(const QMimeData *source)
|
||||
{
|
||||
if (!source->hasImage())
|
||||
{
|
||||
insertPlainText(source->text());
|
||||
}
|
||||
}
|
||||
|
||||
QCompleter *ResizingTextEdit::getCompleter() const
|
||||
{
|
||||
return this->completer_;
|
||||
|
|
|
@ -30,6 +30,9 @@ protected:
|
|||
void focusInEvent(QFocusEvent *event) override;
|
||||
void focusOutEvent(QFocusEvent *event) override;
|
||||
|
||||
bool canInsertFromMimeData(const QMimeData *source) const override;
|
||||
void insertFromMimeData(const QMimeData *source) override;
|
||||
|
||||
private:
|
||||
// hadSpace is set to true in case the "textUnderCursor" word was after a
|
||||
// space
|
||||
|
|
|
@ -17,6 +17,25 @@ SearchPopup::SearchPopup()
|
|||
this->resize(400, 600);
|
||||
}
|
||||
|
||||
void SearchPopup::setChannel(ChannelPtr channel)
|
||||
{
|
||||
this->snapshot_ = channel->getMessageSnapshot();
|
||||
this->performSearch();
|
||||
|
||||
this->setWindowTitle("Searching in " + channel->getName() + "s history");
|
||||
}
|
||||
|
||||
void SearchPopup::keyPressEvent(QKeyEvent *e)
|
||||
{
|
||||
if (e->key() == Qt::Key_Escape)
|
||||
{
|
||||
this->close();
|
||||
return;
|
||||
}
|
||||
|
||||
BaseWidget::keyPressEvent(e);
|
||||
}
|
||||
|
||||
void SearchPopup::initLayout()
|
||||
{
|
||||
// VBOX
|
||||
|
@ -60,14 +79,6 @@ void SearchPopup::initLayout()
|
|||
}
|
||||
}
|
||||
|
||||
void SearchPopup::setChannel(ChannelPtr channel)
|
||||
{
|
||||
this->snapshot_ = channel->getMessageSnapshot();
|
||||
this->performSearch();
|
||||
|
||||
this->setWindowTitle("Searching in " + channel->getName() + "s history");
|
||||
}
|
||||
|
||||
void SearchPopup::performSearch()
|
||||
{
|
||||
QString text = searchInput_->text();
|
||||
|
|
|
@ -22,6 +22,9 @@ public:
|
|||
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
private:
|
||||
void initLayout();
|
||||
void performSearch();
|
||||
|
|
|
@ -113,6 +113,7 @@ AboutPage::AboutPage()
|
|||
l.emplace<QLabel>("Messenger emojis provided by <a href=\"https://facebook.com\">Facebook</a>")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Emoji datasource provided by <a href=\"https://www.iamcal.com/\">Cal Henderson</a>"
|
||||
"(<a href=\"https://github.com/iamcal/emoji-data/blob/master/LICENSE\">show license</a>)")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Twitch emote data provided by <a href=\"https://twitchemotes.com/\">twitchemotes.com</a> through the <a href=\"https://github.com/Chatterino/api\">Chatterino API</a>")->setOpenExternalLinks(true);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace chatterino {
|
|||
AdvancedPage::AdvancedPage()
|
||||
: SettingsPage("Advanced", ":/settings/advanced.svg")
|
||||
{
|
||||
auto app = getApp();
|
||||
LayoutCreator<AdvancedPage> layoutCreator(this);
|
||||
|
||||
auto tabs = layoutCreator.emplace<QTabWidget>();
|
||||
|
|
|
@ -14,8 +14,6 @@ namespace chatterino {
|
|||
ExternalToolsPage::ExternalToolsPage()
|
||||
: SettingsPage("External tools", ":/settings/externaltools.svg")
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
LayoutCreator<ExternalToolsPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
|
|
|
@ -189,6 +189,9 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
layout.addCheckbox("Show tab close button", s.showTabCloseButton);
|
||||
layout.addCheckbox("Show input when empty", s.showEmptyInput);
|
||||
layout.addCheckbox("Show input message length", s.showMessageLength);
|
||||
layout.addCheckbox("Hide preferences button (ctrl+p to show)",
|
||||
s.hidePreferencesButton);
|
||||
layout.addCheckbox("Hide user button", s.hideUserButton);
|
||||
|
||||
layout.addTitle("Messages");
|
||||
layout.addCheckbox("Timestamps", s.showTimestamps);
|
||||
|
@ -197,8 +200,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
s.timestampFormat, true);
|
||||
layout.addDropdown<int>(
|
||||
"Collapse messages",
|
||||
{"Never", "Longer than 2 lines", "Longer than 3 lines",
|
||||
"Longer than 4 lines", "Longer than 5 lines"},
|
||||
{"Never", "After 2 lines", "After 3 lines", "After 4 lines",
|
||||
"After 5 lines"},
|
||||
s.collpseMessagesMinLines,
|
||||
[](auto val) {
|
||||
return val ? QString("After ") + QString::number(val) + " lines"
|
||||
|
@ -209,6 +212,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
layout.addCheckbox("Alternate background color", s.alternateMessages);
|
||||
// layout.addCheckbox("Mark last message you read");
|
||||
// layout.addDropdown("Last read message style", {"Default"});
|
||||
layout.addCheckbox("Hide moderated messages", s.hideModerated);
|
||||
layout.addCheckbox("Hide moderation messages", s.hideModerationActions);
|
||||
|
||||
layout.addTitle("Emotes");
|
||||
layout.addDropdown<float>(
|
||||
|
@ -223,6 +228,7 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
[](auto args) { return fuzzyToFloat(args.value, 1.f); });
|
||||
layout.addCheckbox("Gif animations", s.animateEmotes);
|
||||
layout.addCheckbox("Animate only when focused", s.animationsWhenFocused);
|
||||
layout.addCheckbox("Emote images", s.enableEmoteImages);
|
||||
layout.addDropdown("Emoji set",
|
||||
{"EmojiOne 2", "EmojiOne 3", "Twitter", "Facebook",
|
||||
"Apple", "Google", "Messenger"},
|
||||
|
@ -249,63 +255,35 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
layout.addTitle("Miscellaneous");
|
||||
layout.addCheckbox("Show joined users (< 1000 chatters)", s.showJoins);
|
||||
layout.addCheckbox("Show parted users (< 1000 chatters)", s.showParts);
|
||||
layout.addDropdown("Boldness", {"Not implemented"});
|
||||
layout.addCheckbox("Lowercase domains", s.lowercaseDomains);
|
||||
layout.addCheckbox("Bold @usernames", s.boldUsernames);
|
||||
layout.addDropdown<float>(
|
||||
"Username font weight", {"0", "25", "Default", "75", "100"},
|
||||
s.boldScale,
|
||||
[](auto val) {
|
||||
if (val == 50)
|
||||
return QString("Default");
|
||||
else
|
||||
return QString::number(val);
|
||||
},
|
||||
[](auto args) { return fuzzyToFloat(args.value, 50.f); });
|
||||
layout.addCheckbox("Show link info when hovering", s.linkInfoTooltip);
|
||||
layout.addCheckbox("Double click links to open", s.linksDoubleClickOnly);
|
||||
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
||||
layout.addCheckbox("Show live indicator in tabs", s.showTabLive);
|
||||
layout.addDropdown<int>(
|
||||
"Show emote preview in tooltip on hover",
|
||||
{"Don't show", "Always show", "Hold shift"}, s.emotesTooltipPreview,
|
||||
[](int index) { return index; }, [](auto args) { return args.index; },
|
||||
false);
|
||||
|
||||
layout.addSpacing(16);
|
||||
layout.addSeperator();
|
||||
|
||||
layout.addTitle2("Misc");
|
||||
layout.addTitle2("Miscellaneous (Twitch)");
|
||||
layout.addCheckbox("Show twitch whispers inline", s.inlineWhispers);
|
||||
layout.addDropdown<int>(
|
||||
"Historic messages appearance",
|
||||
{"Crossed and Greyed", "Crossed", "Greyed", "No change"},
|
||||
s.historicMessagesAppearance,
|
||||
[](auto val) {
|
||||
if (val & HistoricMessageAppearance::Crossed &&
|
||||
val & HistoricMessageAppearance::Greyed)
|
||||
{
|
||||
return QString("Crossed and Greyed");
|
||||
}
|
||||
else if (val & HistoricMessageAppearance::Crossed)
|
||||
{
|
||||
return QString("Crossed");
|
||||
}
|
||||
else if (val & HistoricMessageAppearance::Greyed)
|
||||
{
|
||||
return QString("Greyed");
|
||||
}
|
||||
else
|
||||
{
|
||||
return QString("No Change");
|
||||
}
|
||||
},
|
||||
[](auto args) -> int {
|
||||
switch (args.index)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
return HistoricMessageAppearance::Crossed |
|
||||
HistoricMessageAppearance::Greyed;
|
||||
break;
|
||||
case 1:
|
||||
return HistoricMessageAppearance::Crossed;
|
||||
break;
|
||||
case 2:
|
||||
return HistoricMessageAppearance::Greyed;
|
||||
break;
|
||||
case 3:
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
},
|
||||
false);
|
||||
layout.addCheckbox("Emphasize deleted messages", s.redDisabledMessages);
|
||||
layout.addCheckbox("Load message history on connect",
|
||||
s.loadTwitchMessageHistoryOnConnect);
|
||||
|
||||
/*
|
||||
layout.addTitle2("Cache");
|
||||
|
|
|
@ -77,8 +77,24 @@ void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users,
|
|||
|
||||
auto anyways = users.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
anyways.emplace<QLabel>("Show anyways if:");
|
||||
anyways.emplace<QComboBox>();
|
||||
anyways.emplace<QLabel>("Show messages from ignored users anyways:");
|
||||
|
||||
auto combo = anyways.emplace<QComboBox>().getElement();
|
||||
combo->addItems(
|
||||
{"Never", "If you are Moderator", "If you are Broadcaster"});
|
||||
|
||||
auto &setting = getSettings()->showIgnoredUsersMessages;
|
||||
|
||||
setting.connect(
|
||||
[combo](const int value) { combo->setCurrentIndex(value); });
|
||||
|
||||
QObject::connect(combo,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[&setting](int index) {
|
||||
if (index != -1)
|
||||
setting = index;
|
||||
});
|
||||
|
||||
anyways->addStretch(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@ KeyboardSettingsPage::KeyboardSettingsPage()
|
|||
new QLabel("Search in current channel"));
|
||||
form->addRow(new QLabel("Ctrl + E"), new QLabel("Open Emote menu"));
|
||||
form->addRow(new QLabel("Ctrl + P"), new QLabel("Open Settings menu"));
|
||||
form->addRow(new QLabel("F5"), new QLabel("Reload subscriber and channel emotes"));
|
||||
form->addRow(new QLabel("F5"),
|
||||
new QLabel("Reload subscriber and channel emotes"));
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -182,12 +182,6 @@ void LookPage::addMessageTab(LayoutCreator<QVBoxLayout> layout)
|
|||
|
||||
layout.append(
|
||||
this->createCheckBox("Compact emotes", getSettings()->compactEmotes));
|
||||
/// greyOutHistoricMessages setting changed by hemirt from checkbox to
|
||||
/// historicMessagesBehaviour dropdown QString option
|
||||
// layout.append(this->createCheckBox("Grey out historic messages",
|
||||
// getSettings()->greyOutHistoricMessages));
|
||||
///
|
||||
// --
|
||||
layout.emplace<Line>(false);
|
||||
|
||||
// bold-slider
|
||||
|
|
|
@ -61,15 +61,10 @@ QString formatSize(qint64 size)
|
|||
|
||||
QString fetchLogDirectorySize()
|
||||
{
|
||||
QString logPathDirectory;
|
||||
if (getSettings()->logPath == "")
|
||||
{
|
||||
logPathDirectory = getPaths()->messageLogDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
logPathDirectory = getSettings()->logPath;
|
||||
}
|
||||
QString logPathDirectory = getSettings()->logPath.getValue().isEmpty()
|
||||
? getPaths()->messageLogDirectory
|
||||
: getSettings()->logPath;
|
||||
|
||||
qint64 logsSize = dirSize(logPathDirectory);
|
||||
QString logsSizeLabel = "Your logs currently take up ";
|
||||
logsSizeLabel += formatSize(logsSize);
|
||||
|
@ -96,31 +91,22 @@ ModerationPage::ModerationPage()
|
|||
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
||||
|
||||
// Logs (copied from LoggingMananger)
|
||||
getSettings()->logPath.connect(
|
||||
[logsPathLabel](const QString &logPath, auto) mutable {
|
||||
QString pathOriginal;
|
||||
getSettings()->logPath.connect([logsPathLabel](const QString &logPath,
|
||||
auto) mutable {
|
||||
QString pathOriginal =
|
||||
logPath.isEmpty() ? getPaths()->messageLogDirectory : logPath;
|
||||
|
||||
if (logPath == "")
|
||||
{
|
||||
pathOriginal = getPaths()->messageLogDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
pathOriginal = logPath;
|
||||
}
|
||||
QString pathShortened =
|
||||
"Logs are saved at <a href=\"file:///" + pathOriginal +
|
||||
"\"><span style=\"color: white;\">" +
|
||||
shortenString(pathOriginal, 50) + "</span></a>";
|
||||
|
||||
QString pathShortened =
|
||||
"Logs are saved at <a href=\"file:///" + pathOriginal +
|
||||
"\"><span style=\"color: white;\">" +
|
||||
shortenString(pathOriginal, 50) + "</span></a>";
|
||||
|
||||
logsPathLabel->setText(pathShortened);
|
||||
logsPathLabel->setToolTip(pathOriginal);
|
||||
});
|
||||
logsPathLabel->setText(pathShortened);
|
||||
logsPathLabel->setToolTip(pathOriginal);
|
||||
});
|
||||
|
||||
logsPathLabel->setTextFormat(Qt::RichText);
|
||||
logsPathLabel->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
||||
Qt::LinksAccessibleByKeyboard |
|
||||
Qt::LinksAccessibleByKeyboard);
|
||||
logsPathLabel->setOpenExternalLinks(true);
|
||||
logs.append(this->createCheckBox("Enable logging",
|
||||
|
@ -161,7 +147,8 @@ ModerationPage::ModerationPage()
|
|||
// clang-format off
|
||||
auto label = modMode.emplace<QLabel>(
|
||||
"Moderation mode is enabled by clicking <img width='18' height='18' src=':/buttons/modModeDisabled.png'> in a channel that you moderate.<br><br>"
|
||||
"Moderation buttons can be bound to chat commands such as \"/ban {user}\", \"/timeout {user} 1000\" or any other custom text commands.<br>");
|
||||
"Moderation buttons can be bound to chat commands such as \"/ban {user}\", \"/timeout {user} 1000\", \"/w someusername !report {user} was bad in channel {channel}\" or any other custom text commands.<br>"
|
||||
"For deleting messages use /delete {msg-id}.");
|
||||
label->setWordWrap(true);
|
||||
label->setStyleSheet("color: #bbb");
|
||||
// clang-format on
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/notifications/NotificationModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
|
||||
|
@ -23,7 +24,6 @@ NotificationPage::NotificationPage()
|
|||
: SettingsPage("Notifications", ":/settings/notification2.svg")
|
||||
{
|
||||
LayoutCreator<NotificationPage> layoutCreator(this);
|
||||
|
||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||
{
|
||||
auto tabs = layout.emplace<QTabWidget>();
|
||||
|
@ -39,6 +39,23 @@ NotificationPage::NotificationPage()
|
|||
settings.append(
|
||||
this->createCheckBox("Enable toasts (Windows 8 or later)",
|
||||
getSettings()->notificationToast));
|
||||
auto openIn = settings.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
openIn.emplace<QLabel>("Open stream from Toast: ")
|
||||
->setSizePolicy(QSizePolicy::Maximum,
|
||||
QSizePolicy::Preferred);
|
||||
|
||||
// implementation of custom combobox done
|
||||
// because addComboBox only can handle strings-settings
|
||||
// int setting for the ToastReaction is desired
|
||||
openIn
|
||||
.append(this->createToastReactionComboBox(
|
||||
this->managedConnections_))
|
||||
->setSizePolicy(QSizePolicy::Maximum,
|
||||
QSizePolicy::Preferred);
|
||||
}
|
||||
openIn->setContentsMargins(40, 0, 0, 0);
|
||||
openIn->setSizeConstraint(QLayout::SetMaximumSize);
|
||||
#endif
|
||||
auto customSound =
|
||||
layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
|
@ -117,4 +134,31 @@ NotificationPage::NotificationPage()
|
|||
}
|
||||
}
|
||||
}
|
||||
QComboBox *NotificationPage::createToastReactionComboBox(
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections)
|
||||
{
|
||||
QComboBox *toastReactionOptions = new QComboBox();
|
||||
|
||||
for (int i = 0; i <= static_cast<int>(ToastReaction::DontOpen); i++)
|
||||
{
|
||||
toastReactionOptions->insertItem(
|
||||
i, Toasts::findStringFromReaction(static_cast<ToastReaction>(i)));
|
||||
}
|
||||
|
||||
// update when setting changes
|
||||
pajlada::Settings::Setting<int> setting = getSettings()->openFromToast;
|
||||
setting.connect(
|
||||
[toastReactionOptions](const int &index, auto) {
|
||||
toastReactionOptions->setCurrentIndex(index);
|
||||
},
|
||||
managedConnections);
|
||||
|
||||
QObject::connect(toastReactionOptions,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[](const int &newValue) {
|
||||
getSettings()->openFromToast.setValue(newValue);
|
||||
});
|
||||
|
||||
return toastReactionOptions;
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -15,6 +15,8 @@ public:
|
|||
NotificationPage();
|
||||
|
||||
private:
|
||||
QComboBox *createToastReactionComboBox(
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue