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"]
|
[submodule "lib/appbase"]
|
||||||
path = 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:
|
os: osx
|
||||||
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
|
osx_image: xcode10.2
|
||||||
- sudo apt-get update -qq
|
|
||||||
- sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
|
addons:
|
||||||
- sudo apt-get install qt5-default qttools5-dev-tools
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- boost
|
||||||
|
- openssl
|
||||||
|
- rapidjson
|
||||||
|
- qt
|
||||||
|
- p7zip
|
||||||
|
|
||||||
|
compiler: clang
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- qmake -qt=qt5 -v
|
- mkdir build && cd build
|
||||||
- qmake -qt=qt5
|
- /usr/local/opt/qt/bin/qmake .. && make -j8
|
||||||
- make
|
- /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
|
## Ubuntu 18.04
|
||||||
*most likely works the same for other Debian-like distros*
|
*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
|
1. Open `chatterino.pro` with QT Creator and build
|
||||||
|
|
||||||
## Arch Linux
|
## Arch Linux
|
||||||
|
|
|
@ -102,7 +102,14 @@ To produce all supplement files for a standalone build, follow these steps (adju
|
||||||
|
|
||||||
cd C:\Users\example\src\build-chatterino-Desktop_Qt_5_11_2_MSVC2017_64bit-Release\release
|
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
|
C:\Qt\5.11.2\msvc2017_64\bin\windeployqt.exe chatterino.exe
|
||||||
|
|
||||||
|
5. Go to `C:\local\bin\` and copy these dll's into your `release folder`.
|
||||||
|
|
||||||
|
libssl-1_1-x64.dll
|
||||||
|
libcrypto-1_1-x64.dll
|
||||||
|
ssleay32.dll
|
||||||
|
libeay32.dll
|
||||||
|
|
||||||
5. The `releases` directory will now be populated with all the required files to make the chatterino build standalone.
|
6. The `releases` directory will now be populated with all the required files to make the chatterino build standalone.
|
||||||
|
|
||||||
You can now create a zip archive of all the contents in `releases` and distribute the program as is, without requiring any development tools to be present on the target system. (However, the vcredist package must be present, as usual - see the [README](README.md)).
|
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`
|
7. Under `Clang Format` select `Use predefined style: File` and `Fallback style: None`
|
||||||
|
|
||||||
Qt creator should now format the documents when saving it.
|
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
|
827
chatterino.pro
827
chatterino.pro
|
@ -1,403 +1,424 @@
|
||||||
#-------------------------------------------------
|
QT += widgets core gui network multimedia svg concurrent
|
||||||
#
|
CONFIG += communi
|
||||||
# Project created by QtCreator 2016-12-28T18:23:35
|
COMMUNI += core model util
|
||||||
#
|
|
||||||
#-------------------------------------------------
|
INCLUDEPATH += src/
|
||||||
|
TARGET = chatterino
|
||||||
message(----)
|
TEMPLATE = app
|
||||||
|
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
|
||||||
# define project shit
|
CONFIG += precompile_header
|
||||||
QT += widgets core gui network multimedia svg concurrent
|
DEFINES += CHATTERINO
|
||||||
CONFIG += communi
|
DEFINES += "AB_NAMESPACE=chatterino"
|
||||||
COMMUNI += core model util
|
DEFINES += AB_CUSTOM_THEME
|
||||||
|
DEFINES += AB_CUSTOM_SETTINGS
|
||||||
INCLUDEPATH += src/
|
CONFIG += AB_NOT_STANDALONE
|
||||||
TARGET = chatterino
|
|
||||||
TEMPLATE = app
|
useBreakpad {
|
||||||
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
|
LIBS += -L$$PWD/lib/qBreakpad/handler/build
|
||||||
CONFIG += precompile_header
|
include(lib/qBreakpad/qBreakpad.pri)
|
||||||
DEFINES += CHATTERINO
|
DEFINES += C_USE_BREAKPAD
|
||||||
DEFINES += "AB_NAMESPACE=chatterino"
|
}
|
||||||
DEFINES += AB_CUSTOM_THEME
|
|
||||||
DEFINES += AB_CUSTOM_SETTINGS
|
# https://bugreports.qt.io/browse/QTBUG-27018
|
||||||
CONFIG += AB_NOT_STANDALONE
|
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
||||||
|
TARGET = bin/chatterino
|
||||||
useBreakpad {
|
}
|
||||||
LIBS += -L$$PWD/lib/qBreakpad/handler/build
|
|
||||||
include(lib/qBreakpad/qBreakpad.pri)
|
# Icons
|
||||||
DEFINES += C_USE_BREAKPAD
|
macx:ICON = resources/chatterino.icns
|
||||||
}
|
win32:RC_FILE = resources/windows.rc
|
||||||
|
|
||||||
# https://bugreports.qt.io/browse/QTBUG-27018
|
macx {
|
||||||
equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
|
LIBS += -L/usr/local/lib
|
||||||
TARGET = bin/chatterino
|
}
|
||||||
}
|
|
||||||
|
# Submodules
|
||||||
# Icons
|
include(lib/appbase.pri)
|
||||||
#macx:ICON = resources/images/chatterino2.icns
|
include(lib/humanize.pri)
|
||||||
win32:RC_FILE = resources/windows.rc
|
DEFINES += IRC_NAMESPACE=Communi
|
||||||
|
include(lib/libcommuni.pri)
|
||||||
macx {
|
include(lib/websocketpp.pri)
|
||||||
LIBS += -L/usr/local/lib
|
include(lib/openssl.pri)
|
||||||
}
|
include(lib/wintoast.pri)
|
||||||
|
|
||||||
# Submodules
|
# Optional feature: QtWebEngine
|
||||||
include(lib/appbase.pri)
|
#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
|
||||||
include(lib/humanize.pri)
|
# message(Using QWebEngine)
|
||||||
DEFINES += IRC_NAMESPACE=Communi
|
# QT += webenginewidgets
|
||||||
include(lib/libcommuni.pri)
|
# DEFINES += "USEWEBENGINE"
|
||||||
include(lib/websocketpp.pri)
|
#}
|
||||||
include(lib/openssl.pri)
|
|
||||||
include(lib/wintoast.pri)
|
SOURCES += \
|
||||||
|
src/Application.cpp \
|
||||||
# Optional feature: QtWebEngine
|
src/autogenerated/ResourcesAutogen.cpp \
|
||||||
#exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
|
src/BrowserExtension.cpp \
|
||||||
# message(Using QWebEngine)
|
src/common/Channel.cpp \
|
||||||
# QT += webenginewidgets
|
src/common/CompletionModel.cpp \
|
||||||
# DEFINES += "USEWEBENGINE"
|
src/common/DownloadManager.cpp \
|
||||||
#}
|
src/common/Env.cpp \
|
||||||
|
src/common/LinkParser.cpp \
|
||||||
SOURCES += \
|
src/common/NetworkData.cpp \
|
||||||
src/Application.cpp \
|
src/common/NetworkManager.cpp \
|
||||||
src/common/Channel.cpp \
|
src/common/NetworkRequest.cpp \
|
||||||
src/common/CompletionModel.cpp \
|
src/common/NetworkResult.cpp \
|
||||||
src/common/NetworkData.cpp \
|
src/common/NetworkTimer.cpp \
|
||||||
src/common/NetworkManager.cpp \
|
src/common/UsernameSet.cpp \
|
||||||
src/common/NetworkRequest.cpp \
|
src/controllers/accounts/Account.cpp \
|
||||||
src/common/NetworkResult.cpp \
|
src/controllers/accounts/AccountController.cpp \
|
||||||
src/common/NetworkTimer.cpp \
|
src/controllers/accounts/AccountModel.cpp \
|
||||||
src/controllers/accounts/Account.cpp \
|
src/controllers/commands/Command.cpp \
|
||||||
src/controllers/accounts/AccountController.cpp \
|
src/controllers/commands/CommandController.cpp \
|
||||||
src/controllers/accounts/AccountModel.cpp \
|
src/controllers/commands/CommandModel.cpp \
|
||||||
src/controllers/commands/Command.cpp \
|
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||||
src/controllers/commands/CommandController.cpp \
|
src/controllers/highlights/HighlightController.cpp \
|
||||||
src/controllers/commands/CommandModel.cpp \
|
src/controllers/highlights/HighlightModel.cpp \
|
||||||
src/controllers/highlights/HighlightController.cpp \
|
src/controllers/highlights/UserHighlightModel.cpp \
|
||||||
src/controllers/highlights/HighlightModel.cpp \
|
src/controllers/ignores/IgnoreController.cpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
src/controllers/ignores/IgnoreModel.cpp \
|
||||||
src/controllers/highlights/UserHighlightModel.cpp \
|
src/controllers/moderationactions/ModerationAction.cpp \
|
||||||
src/controllers/ignores/IgnoreController.cpp \
|
src/controllers/moderationactions/ModerationActionModel.cpp \
|
||||||
src/controllers/ignores/IgnoreModel.cpp \
|
src/controllers/moderationactions/ModerationActions.cpp \
|
||||||
src/controllers/notifications/NotificationController.cpp \
|
src/controllers/notifications/NotificationController.cpp \
|
||||||
src/controllers/taggedusers/TaggedUser.cpp \
|
src/controllers/notifications/NotificationModel.cpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.cpp \
|
src/controllers/taggedusers/TaggedUser.cpp \
|
||||||
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
src/controllers/taggedusers/TaggedUsersController.cpp \
|
||||||
src/main.cpp \
|
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
||||||
src/messages/Image.cpp \
|
src/main.cpp \
|
||||||
src/messages/layouts/MessageLayout.cpp \
|
src/messages/Emote.cpp \
|
||||||
src/messages/layouts/MessageLayoutContainer.cpp \
|
src/messages/Image.cpp \
|
||||||
src/messages/layouts/MessageLayoutElement.cpp \
|
src/messages/ImageSet.cpp \
|
||||||
src/messages/Link.cpp \
|
src/messages/layouts/MessageLayout.cpp \
|
||||||
src/messages/Message.cpp \
|
src/messages/layouts/MessageLayoutContainer.cpp \
|
||||||
src/messages/MessageBuilder.cpp \
|
src/messages/layouts/MessageLayoutElement.cpp \
|
||||||
src/messages/MessageColor.cpp \
|
src/messages/Link.cpp \
|
||||||
src/messages/MessageElement.cpp \
|
src/messages/Message.cpp \
|
||||||
src/providers/emoji/Emojis.cpp \
|
src/messages/MessageBuilder.cpp \
|
||||||
src/providers/irc/AbstractIrcServer.cpp \
|
src/messages/MessageColor.cpp \
|
||||||
src/providers/irc/IrcAccount.cpp \
|
src/messages/MessageContainer.cpp \
|
||||||
src/providers/irc/IrcChannel2.cpp \
|
src/messages/MessageElement.cpp \
|
||||||
src/providers/irc/IrcConnection2.cpp \
|
src/providers/bttv/BttvEmotes.cpp \
|
||||||
src/providers/irc/IrcServer.cpp \
|
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
||||||
src/providers/twitch/IrcMessageHandler.cpp \
|
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||||
src/providers/twitch/PartialTwitchUser.cpp \
|
src/providers/emoji/Emojis.cpp \
|
||||||
src/providers/twitch/PubsubActions.cpp \
|
src/providers/ffz/FfzEmotes.cpp \
|
||||||
src/providers/twitch/PubsubHelpers.cpp \
|
src/providers/ffz/FfzModBadge.cpp \
|
||||||
src/providers/twitch/TwitchAccount.cpp \
|
src/providers/irc/AbstractIrcServer.cpp \
|
||||||
src/providers/twitch/TwitchAccountManager.cpp \
|
src/providers/irc/IrcAccount.cpp \
|
||||||
src/providers/twitch/TwitchChannel.cpp \
|
src/providers/irc/IrcChannel2.cpp \
|
||||||
src/providers/twitch/TwitchEmotes.cpp \
|
src/providers/irc/IrcConnection2.cpp \
|
||||||
src/providers/twitch/TwitchHelpers.cpp \
|
src/providers/irc/IrcServer.cpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.cpp \
|
src/providers/LinkResolver.cpp \
|
||||||
src/providers/twitch/TwitchServer.cpp \
|
src/providers/twitch/ChatroomChannel.cpp \
|
||||||
src/providers/twitch/TwitchUser.cpp \
|
src/providers/twitch/IrcMessageHandler.cpp \
|
||||||
src/singletons/helper/GifTimer.cpp \
|
src/providers/twitch/PartialTwitchUser.cpp \
|
||||||
src/singletons/helper/LoggingChannel.cpp \
|
src/providers/twitch/PubsubActions.cpp \
|
||||||
src/controllers/moderationactions/ModerationAction.cpp \
|
src/providers/twitch/PubsubClient.cpp \
|
||||||
src/singletons/WindowManager.cpp \
|
src/providers/twitch/PubsubHelpers.cpp \
|
||||||
src/util/DebugCount.cpp \
|
src/providers/twitch/TwitchAccount.cpp \
|
||||||
src/util/RapidjsonHelpers.cpp \
|
src/providers/twitch/TwitchAccountManager.cpp \
|
||||||
src/util/StreamLink.cpp \
|
src/providers/twitch/TwitchApi.cpp \
|
||||||
src/widgets/AccountSwitchPopupWidget.cpp \
|
src/providers/twitch/TwitchBadges.cpp \
|
||||||
src/widgets/AccountSwitchWidget.cpp \
|
src/providers/twitch/TwitchChannel.cpp \
|
||||||
src/widgets/AttachedWindow.cpp \
|
src/providers/twitch/TwitchEmotes.cpp \
|
||||||
src/widgets/dialogs/EmotePopup.cpp \
|
src/providers/twitch/TwitchHelpers.cpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
src/providers/twitch/TwitchMessageBuilder.cpp \
|
||||||
src/widgets/dialogs/LoginDialog.cpp \
|
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
||||||
src/widgets/dialogs/LogsPopup.cpp \
|
src/providers/twitch/TwitchServer.cpp \
|
||||||
src/widgets/dialogs/NotificationPopup.cpp \
|
src/providers/twitch/TwitchUser.cpp \
|
||||||
src/widgets/dialogs/QualityPopup.cpp \
|
src/RunGui.cpp \
|
||||||
src/widgets/dialogs/SelectChannelDialog.cpp \
|
src/singletons/Badges.cpp \
|
||||||
src/widgets/dialogs/SettingsDialog.cpp \
|
src/singletons/Emotes.cpp \
|
||||||
src/widgets/dialogs/TextInputDialog.cpp \
|
src/singletons/helper/GifTimer.cpp \
|
||||||
src/widgets/dialogs/UserInfoPopup.cpp \
|
src/singletons/helper/LoggingChannel.cpp \
|
||||||
src/widgets/dialogs/WelcomeDialog.cpp \
|
src/singletons/Logging.cpp \
|
||||||
src/widgets/helper/ChannelView.cpp \
|
src/singletons/NativeMessaging.cpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
src/singletons/Paths.cpp \
|
||||||
src/widgets/helper/DebugPopup.cpp \
|
src/singletons/Resources.cpp \
|
||||||
src/widgets/helper/EditableModelView.cpp \
|
src/singletons/Settings.cpp \
|
||||||
src/widgets/helper/NotebookButton.cpp \
|
src/singletons/Theme.cpp \
|
||||||
src/widgets/helper/NotebookTab.cpp \
|
src/singletons/Toasts.cpp \
|
||||||
src/widgets/helper/ResizingTextEdit.cpp \
|
src/singletons/Updates.cpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
src/singletons/WindowManager.cpp \
|
||||||
src/widgets/helper/SearchPopup.cpp \
|
src/singletons/TooltipPreviewImage.cpp \
|
||||||
src/widgets/helper/SettingsDialogTab.cpp \
|
src/util/DebugCount.cpp \
|
||||||
src/widgets/Notebook.cpp \
|
src/util/FormatTime.cpp \
|
||||||
src/widgets/Scrollbar.cpp \
|
src/util/IncognitoBrowser.cpp \
|
||||||
src/widgets/settingspages/AboutPage.cpp \
|
src/util/InitUpdateButton.cpp \
|
||||||
src/widgets/settingspages/AccountsPage.cpp \
|
src/util/JsonQuery.cpp \
|
||||||
src/widgets/settingspages/BrowserExtensionPage.cpp \
|
src/util/RapidjsonHelpers.cpp \
|
||||||
src/widgets/settingspages/CommandPage.cpp \
|
src/util/StreamLink.cpp \
|
||||||
src/widgets/settingspages/EmotesPage.cpp \
|
src/widgets/AccountSwitchPopupWidget.cpp \
|
||||||
src/widgets/settingspages/ExternalToolsPage.cpp \
|
src/widgets/AccountSwitchWidget.cpp \
|
||||||
src/widgets/settingspages/HighlightingPage.cpp \
|
src/widgets/AttachedWindow.cpp \
|
||||||
src/widgets/settingspages/KeyboardSettingsPage.cpp \
|
src/widgets/dialogs/EmotePopup.cpp \
|
||||||
src/widgets/settingspages/LogsPage.cpp \
|
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||||
src/widgets/settingspages/ModerationPage.cpp \
|
src/widgets/dialogs/LoginDialog.cpp \
|
||||||
src/widgets/settingspages/NotificationPage.cpp \
|
src/widgets/dialogs/LogsPopup.cpp \
|
||||||
src/widgets/settingspages/SettingsPage.cpp \
|
src/widgets/dialogs/NotificationPopup.cpp \
|
||||||
src/widgets/settingspages/SpecialChannelsPage.cpp \
|
src/widgets/dialogs/QualityPopup.cpp \
|
||||||
src/widgets/splits/Split.cpp \
|
src/widgets/dialogs/SelectChannelDialog.cpp \
|
||||||
src/widgets/splits/SplitContainer.cpp \
|
src/widgets/dialogs/SettingsDialog.cpp \
|
||||||
src/widgets/splits/SplitHeader.cpp \
|
src/widgets/dialogs/TextInputDialog.cpp \
|
||||||
src/widgets/splits/SplitInput.cpp \
|
src/widgets/dialogs/UpdateDialog.cpp \
|
||||||
src/widgets/splits/SplitOverlay.cpp \
|
src/widgets/dialogs/UserInfoPopup.cpp \
|
||||||
src/widgets/StreamView.cpp \
|
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||||
src/widgets/Window.cpp \
|
src/widgets/helper/ChannelView.cpp \
|
||||||
src/common/LinkParser.cpp \
|
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||||
src/controllers/moderationactions/ModerationActions.cpp \
|
src/widgets/helper/DebugPopup.cpp \
|
||||||
src/singletons/NativeMessaging.cpp \
|
src/widgets/helper/EditableModelView.cpp \
|
||||||
src/singletons/Emotes.cpp \
|
src/widgets/helper/NotebookButton.cpp \
|
||||||
src/singletons/Logging.cpp \
|
src/widgets/helper/NotebookTab.cpp \
|
||||||
src/singletons/Paths.cpp \
|
src/widgets/helper/ResizingTextEdit.cpp \
|
||||||
src/singletons/Resources.cpp \
|
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||||
src/singletons/Settings.cpp \
|
src/widgets/helper/SearchPopup.cpp \
|
||||||
src/singletons/Updates.cpp \
|
src/widgets/helper/SettingsDialogTab.cpp \
|
||||||
src/singletons/Theme.cpp \
|
src/widgets/Notebook.cpp \
|
||||||
src/controllers/moderationactions/ModerationActionModel.cpp \
|
src/widgets/Scrollbar.cpp \
|
||||||
src/widgets/settingspages/LookPage.cpp \
|
src/widgets/settingspages/AboutPage.cpp \
|
||||||
src/widgets/settingspages/FeelPage.cpp \
|
src/widgets/settingspages/AccountsPage.cpp \
|
||||||
src/util/InitUpdateButton.cpp \
|
src/widgets/settingspages/AdvancedPage.cpp \
|
||||||
src/widgets/dialogs/UpdateDialog.cpp \
|
src/widgets/settingspages/BrowserExtensionPage.cpp \
|
||||||
src/widgets/settingspages/IgnoresPage.cpp \
|
src/widgets/settingspages/CommandPage.cpp \
|
||||||
src/providers/twitch/PubsubClient.cpp \
|
src/widgets/settingspages/EmotesPage.cpp \
|
||||||
src/providers/twitch/TwitchApi.cpp \
|
src/widgets/settingspages/ExternalToolsPage.cpp \
|
||||||
src/messages/Emote.cpp \
|
src/widgets/settingspages/FeelPage.cpp \
|
||||||
src/messages/ImageSet.cpp \
|
src/widgets/settingspages/GeneralPage.cpp \
|
||||||
src/providers/bttv/BttvEmotes.cpp \
|
src/widgets/settingspages/HighlightingPage.cpp \
|
||||||
src/providers/LinkResolver.cpp \
|
src/widgets/settingspages/IgnoresPage.cpp \
|
||||||
src/providers/ffz/FfzEmotes.cpp \
|
src/widgets/settingspages/KeyboardSettingsPage.cpp \
|
||||||
src/autogenerated/ResourcesAutogen.cpp \
|
src/widgets/settingspages/LogsPage.cpp \
|
||||||
src/singletons/Badges.cpp \
|
src/widgets/settingspages/LookPage.cpp \
|
||||||
src/providers/twitch/TwitchBadges.cpp \
|
src/widgets/settingspages/ModerationPage.cpp \
|
||||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
src/widgets/settingspages/NotificationPage.cpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.cpp \
|
src/widgets/settingspages/SettingsPage.cpp \
|
||||||
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
src/widgets/settingspages/SpecialChannelsPage.cpp \
|
||||||
src/util/JsonQuery.cpp \
|
src/widgets/splits/ClosedSplits.cpp \
|
||||||
src/RunGui.cpp \
|
src/widgets/splits/Split.cpp \
|
||||||
src/BrowserExtension.cpp \
|
src/widgets/splits/SplitContainer.cpp \
|
||||||
src/util/FormatTime.cpp \
|
src/widgets/splits/SplitHeader.cpp \
|
||||||
src/controllers/notifications/NotificationModel.cpp \
|
src/widgets/splits/SplitInput.cpp \
|
||||||
src/singletons/Toasts.cpp \
|
src/widgets/splits/SplitOverlay.cpp \
|
||||||
src/common/DownloadManager.cpp \
|
src/widgets/StreamView.cpp \
|
||||||
src/messages/MessageContainer.cpp \
|
src/widgets/Window.cpp \
|
||||||
src/common/UsernameSet.cpp \
|
src/controllers/pings/PingController.cpp \
|
||||||
src/widgets/settingspages/AdvancedPage.cpp \
|
src/controllers/pings/PingModel.cpp \
|
||||||
src/util/IncognitoBrowser.cpp \
|
|
||||||
src/widgets/splits/ClosedSplits.cpp \
|
HEADERS += \
|
||||||
src/providers/ffz/FfzModBadge.cpp \
|
src/Application.hpp \
|
||||||
src/widgets/settingspages/GeneralPage.cpp \
|
src/autogenerated/ResourcesAutogen.hpp \
|
||||||
src/providers/twitch/ChatroomChannel.cpp
|
src/BrowserExtension.hpp \
|
||||||
|
src/common/Aliases.hpp \
|
||||||
HEADERS += \
|
src/common/Atomic.hpp \
|
||||||
src/Application.hpp \
|
src/common/Channel.hpp \
|
||||||
src/common/Channel.hpp \
|
src/common/Common.hpp \
|
||||||
src/common/Common.hpp \
|
src/common/CompletionModel.hpp \
|
||||||
src/common/CompletionModel.hpp \
|
src/common/ConcurrentMap.hpp \
|
||||||
src/common/Atomic.hpp \
|
src/common/DownloadManager.hpp \
|
||||||
src/common/NetworkCommon.hpp \
|
src/common/LinkParser.hpp \
|
||||||
src/common/NetworkData.hpp \
|
src/common/NetworkCommon.hpp \
|
||||||
src/common/NetworkManager.hpp \
|
src/common/NetworkData.hpp \
|
||||||
src/common/NetworkRequest.hpp \
|
src/common/NetworkManager.hpp \
|
||||||
src/common/NetworkRequester.hpp \
|
src/common/NetworkRequest.hpp \
|
||||||
src/common/NetworkResult.hpp \
|
src/common/NetworkRequester.hpp \
|
||||||
src/common/NetworkTimer.hpp \
|
src/common/NetworkResult.hpp \
|
||||||
src/common/NetworkWorker.hpp \
|
src/common/NetworkTimer.hpp \
|
||||||
src/common/NullablePtr.hpp \
|
src/common/NetworkWorker.hpp \
|
||||||
src/common/ProviderId.hpp \
|
src/common/NullablePtr.hpp \
|
||||||
src/common/SignalVectorModel.hpp \
|
src/common/ProviderId.hpp \
|
||||||
src/common/Version.hpp \
|
src/common/SignalVector.hpp \
|
||||||
src/controllers/accounts/Account.hpp \
|
src/common/SignalVectorModel.hpp \
|
||||||
src/controllers/accounts/AccountController.hpp \
|
src/common/UniqueAccess.hpp \
|
||||||
src/controllers/accounts/AccountModel.hpp \
|
src/common/UsernameSet.hpp \
|
||||||
src/controllers/commands/Command.hpp \
|
src/common/Version.hpp \
|
||||||
src/controllers/commands/CommandController.hpp \
|
src/controllers/accounts/Account.hpp \
|
||||||
src/controllers/commands/CommandModel.hpp \
|
src/controllers/accounts/AccountController.hpp \
|
||||||
src/controllers/highlights/HighlightController.hpp \
|
src/controllers/accounts/AccountModel.hpp \
|
||||||
src/controllers/highlights/HighlightModel.hpp \
|
src/controllers/commands/Command.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
src/controllers/commands/CommandController.hpp \
|
||||||
src/controllers/highlights/HighlightPhrase.hpp \
|
src/controllers/commands/CommandModel.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
||||||
src/controllers/highlights/UserHighlightModel.hpp \
|
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
||||||
src/controllers/ignores/IgnoreController.hpp \
|
src/controllers/highlights/HighlightController.hpp \
|
||||||
src/controllers/ignores/IgnoreModel.hpp \
|
src/controllers/highlights/HighlightModel.hpp \
|
||||||
src/controllers/ignores/IgnorePhrase.hpp \
|
src/controllers/highlights/HighlightPhrase.hpp \
|
||||||
src/controllers/notifications/NotificationController.hpp \
|
src/controllers/highlights/UserHighlightModel.hpp \
|
||||||
src/controllers/taggedusers/TaggedUser.hpp \
|
src/controllers/ignores/IgnoreController.hpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
src/controllers/ignores/IgnoreModel.hpp \
|
||||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
src/controllers/ignores/IgnorePhrase.hpp \
|
||||||
src/messages/Image.hpp \
|
src/controllers/moderationactions/ModerationAction.hpp \
|
||||||
src/messages/layouts/MessageLayout.hpp \
|
src/controllers/moderationactions/ModerationActionModel.hpp \
|
||||||
src/messages/layouts/MessageLayoutContainer.hpp \
|
src/controllers/moderationactions/ModerationActions.hpp \
|
||||||
src/messages/layouts/MessageLayoutElement.hpp \
|
src/controllers/notifications/NotificationController.hpp \
|
||||||
src/messages/LimitedQueue.hpp \
|
src/controllers/notifications/NotificationModel.hpp \
|
||||||
src/messages/LimitedQueueSnapshot.hpp \
|
src/controllers/taggedusers/TaggedUser.hpp \
|
||||||
src/messages/Link.hpp \
|
src/controllers/taggedusers/TaggedUsersController.hpp \
|
||||||
src/messages/Message.hpp \
|
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||||
src/messages/MessageBuilder.hpp \
|
src/messages/Emote.hpp \
|
||||||
src/messages/MessageColor.hpp \
|
src/messages/Image.hpp \
|
||||||
src/messages/MessageElement.hpp \
|
src/messages/ImageSet.hpp \
|
||||||
src/messages/Selection.hpp \
|
src/messages/layouts/MessageLayout.hpp \
|
||||||
src/PrecompiledHeader.hpp \
|
src/messages/layouts/MessageLayoutContainer.hpp \
|
||||||
src/providers/emoji/Emojis.hpp \
|
src/messages/layouts/MessageLayoutElement.hpp \
|
||||||
src/providers/irc/AbstractIrcServer.hpp \
|
src/messages/LimitedQueue.hpp \
|
||||||
src/providers/irc/IrcAccount.hpp \
|
src/messages/LimitedQueueSnapshot.hpp \
|
||||||
src/providers/irc/IrcChannel2.hpp \
|
src/messages/Link.hpp \
|
||||||
src/providers/irc/IrcConnection2.hpp \
|
src/messages/Message.hpp \
|
||||||
src/providers/irc/IrcServer.hpp \
|
src/messages/MessageBuilder.hpp \
|
||||||
src/providers/twitch/EmoteValue.hpp \
|
src/messages/MessageColor.hpp \
|
||||||
src/providers/twitch/IrcMessageHandler.hpp \
|
src/messages/MessageContainer.hpp \
|
||||||
src/providers/twitch/PartialTwitchUser.hpp \
|
src/messages/MessageElement.hpp \
|
||||||
src/providers/twitch/PubsubActions.hpp \
|
src/messages/MessageParseArgs.hpp \
|
||||||
src/providers/twitch/PubsubHelpers.hpp \
|
src/messages/Selection.hpp \
|
||||||
src/providers/twitch/TwitchAccount.hpp \
|
src/PrecompiledHeader.hpp \
|
||||||
src/providers/twitch/TwitchAccountManager.hpp \
|
src/providers/bttv/BttvEmotes.hpp \
|
||||||
src/providers/twitch/TwitchChannel.hpp \
|
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
||||||
src/providers/twitch/TwitchEmotes.hpp \
|
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||||
src/providers/twitch/TwitchHelpers.hpp \
|
src/providers/emoji/Emojis.hpp \
|
||||||
src/providers/twitch/TwitchMessageBuilder.hpp \
|
src/providers/ffz/FfzEmotes.hpp \
|
||||||
src/providers/twitch/TwitchServer.hpp \
|
src/providers/ffz/FfzModBadge.hpp \
|
||||||
src/providers/twitch/TwitchUser.hpp \
|
src/providers/irc/AbstractIrcServer.hpp \
|
||||||
src/singletons/helper/GifTimer.hpp \
|
src/providers/irc/IrcAccount.hpp \
|
||||||
src/singletons/helper/LoggingChannel.hpp \
|
src/providers/irc/IrcChannel2.hpp \
|
||||||
src/controllers/moderationactions/ModerationAction.hpp \
|
src/providers/irc/IrcConnection2.hpp \
|
||||||
src/singletons/WindowManager.hpp \
|
src/providers/irc/IrcServer.hpp \
|
||||||
src/util/ConcurrentMap.hpp \
|
src/providers/LinkResolver.hpp \
|
||||||
src/util/DebugCount.hpp \
|
src/providers/twitch/ChatroomChannel.hpp \
|
||||||
src/util/IrcHelpers.hpp \
|
src/providers/twitch/EmoteValue.hpp \
|
||||||
src/util/LayoutCreator.hpp \
|
src/providers/twitch/IrcMessageHandler.hpp \
|
||||||
src/util/QStringHash.hpp \
|
src/providers/twitch/PartialTwitchUser.hpp \
|
||||||
src/util/RapidjsonHelpers.hpp \
|
src/providers/twitch/PubsubActions.hpp \
|
||||||
src/util/RemoveScrollAreaBackground.hpp \
|
src/providers/twitch/PubsubClient.hpp \
|
||||||
src/util/SharedPtrElementLess.hpp \
|
src/providers/twitch/PubsubHelpers.hpp \
|
||||||
src/util/StandardItemHelper.hpp \
|
src/providers/twitch/TwitchAccount.hpp \
|
||||||
src/util/StreamLink.hpp \
|
src/providers/twitch/TwitchAccountManager.hpp \
|
||||||
src/widgets/AccountSwitchPopupWidget.hpp \
|
src/providers/twitch/TwitchApi.hpp \
|
||||||
src/widgets/AccountSwitchWidget.hpp \
|
src/providers/twitch/TwitchBadges.hpp \
|
||||||
src/widgets/AttachedWindow.hpp \
|
src/providers/twitch/TwitchChannel.hpp \
|
||||||
src/widgets/dialogs/EmotePopup.hpp \
|
src/providers/twitch/TwitchCommon.hpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
src/providers/twitch/TwitchEmotes.hpp \
|
||||||
src/widgets/dialogs/LoginDialog.hpp \
|
src/providers/twitch/TwitchHelpers.hpp \
|
||||||
src/widgets/dialogs/LogsPopup.hpp \
|
src/providers/twitch/TwitchMessageBuilder.hpp \
|
||||||
src/widgets/dialogs/NotificationPopup.hpp \
|
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
||||||
src/widgets/dialogs/QualityPopup.hpp \
|
src/providers/twitch/TwitchServer.hpp \
|
||||||
src/widgets/dialogs/SelectChannelDialog.hpp \
|
src/providers/twitch/TwitchUser.hpp \
|
||||||
src/widgets/dialogs/SettingsDialog.hpp \
|
src/RunGui.hpp \
|
||||||
src/widgets/dialogs/TextInputDialog.hpp \
|
src/singletons/TooltipPreviewImage.hpp \
|
||||||
src/widgets/dialogs/UserInfoPopup.hpp \
|
src/singletons/Badges.hpp \
|
||||||
src/widgets/dialogs/WelcomeDialog.hpp \
|
src/singletons/Emotes.hpp \
|
||||||
src/widgets/helper/ChannelView.hpp \
|
src/singletons/helper/GifTimer.hpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
src/singletons/helper/LoggingChannel.hpp \
|
||||||
src/widgets/helper/DebugPopup.hpp \
|
src/singletons/Logging.hpp \
|
||||||
src/widgets/helper/EditableModelView.hpp \
|
src/singletons/NativeMessaging.hpp \
|
||||||
src/widgets/helper/Line.hpp \
|
src/singletons/Paths.hpp \
|
||||||
src/widgets/helper/NotebookButton.hpp \
|
src/singletons/Resources.hpp \
|
||||||
src/widgets/helper/NotebookTab.hpp \
|
src/singletons/Settings.hpp \
|
||||||
src/widgets/helper/ResizingTextEdit.hpp \
|
src/singletons/Theme.hpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.hpp \
|
src/singletons/Toasts.hpp \
|
||||||
src/widgets/helper/SearchPopup.hpp \
|
src/singletons/Updates.hpp \
|
||||||
src/widgets/helper/SettingsDialogTab.hpp \
|
src/singletons/WindowManager.hpp \
|
||||||
src/widgets/Notebook.hpp \
|
src/util/ConcurrentMap.hpp \
|
||||||
src/widgets/Scrollbar.hpp \
|
src/util/DebugCount.hpp \
|
||||||
src/widgets/settingspages/AboutPage.hpp \
|
src/util/FormatTime.hpp \
|
||||||
src/widgets/settingspages/AccountsPage.hpp \
|
src/util/IncognitoBrowser.hpp \
|
||||||
src/widgets/settingspages/BrowserExtensionPage.hpp \
|
src/util/InitUpdateButton.hpp \
|
||||||
src/widgets/settingspages/CommandPage.hpp \
|
src/util/IrcHelpers.hpp \
|
||||||
src/widgets/settingspages/EmotesPage.hpp \
|
src/util/IsBigEndian.hpp \
|
||||||
src/widgets/settingspages/ExternalToolsPage.hpp \
|
src/util/JsonQuery.hpp \
|
||||||
src/widgets/settingspages/HighlightingPage.hpp \
|
src/util/LayoutCreator.hpp \
|
||||||
src/widgets/settingspages/KeyboardSettingsPage.hpp \
|
src/util/QStringHash.hpp \
|
||||||
src/widgets/settingspages/LogsPage.hpp \
|
src/util/rangealgorithm.hpp \
|
||||||
src/widgets/settingspages/ModerationPage.hpp \
|
src/util/RapidjsonHelpers.hpp \
|
||||||
src/widgets/settingspages/NotificationPage.hpp \
|
src/util/RemoveScrollAreaBackground.hpp \
|
||||||
src/widgets/settingspages/SettingsPage.hpp \
|
src/util/SharedPtrElementLess.hpp \
|
||||||
src/widgets/settingspages/SpecialChannelsPage.hpp \
|
src/util/StandardItemHelper.hpp \
|
||||||
src/widgets/splits/Split.hpp \
|
src/util/StreamLink.hpp \
|
||||||
src/widgets/splits/SplitContainer.hpp \
|
src/widgets/AccountSwitchPopupWidget.hpp \
|
||||||
src/widgets/splits/SplitHeader.hpp \
|
src/widgets/AccountSwitchWidget.hpp \
|
||||||
src/widgets/splits/SplitInput.hpp \
|
src/widgets/AttachedWindow.hpp \
|
||||||
src/widgets/splits/SplitOverlay.hpp \
|
src/widgets/dialogs/EmotePopup.hpp \
|
||||||
src/widgets/StreamView.hpp \
|
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||||
src/widgets/Window.hpp \
|
src/widgets/dialogs/LoginDialog.hpp \
|
||||||
src/providers/twitch/TwitchCommon.hpp \
|
src/widgets/dialogs/LogsPopup.hpp \
|
||||||
src/util/IsBigEndian.hpp \
|
src/widgets/dialogs/NotificationPopup.hpp \
|
||||||
src/common/LinkParser.hpp \
|
src/widgets/dialogs/QualityPopup.hpp \
|
||||||
src/controllers/moderationactions/ModerationActions.hpp \
|
src/widgets/dialogs/SelectChannelDialog.hpp \
|
||||||
src/singletons/Emotes.hpp \
|
src/widgets/dialogs/SettingsDialog.hpp \
|
||||||
src/singletons/Logging.hpp \
|
src/widgets/dialogs/TextInputDialog.hpp \
|
||||||
src/singletons/Paths.hpp \
|
src/widgets/dialogs/UpdateDialog.hpp \
|
||||||
src/singletons/Resources.hpp \
|
src/widgets/dialogs/UserInfoPopup.hpp \
|
||||||
src/singletons/Settings.hpp \
|
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||||
src/singletons/Updates.hpp \
|
src/widgets/helper/ChannelView.hpp \
|
||||||
src/singletons/NativeMessaging.hpp \
|
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||||
src/singletons/Theme.hpp \
|
src/widgets/helper/CommonTexts.hpp \
|
||||||
src/common/SignalVector.hpp \
|
src/widgets/helper/DebugPopup.hpp \
|
||||||
src/widgets/dialogs/LogsPopup.hpp \
|
src/widgets/helper/EditableModelView.hpp \
|
||||||
src/controllers/moderationactions/ModerationActionModel.hpp \
|
src/widgets/helper/Line.hpp \
|
||||||
src/widgets/settingspages/LookPage.hpp \
|
src/widgets/helper/NotebookButton.hpp \
|
||||||
src/widgets/settingspages/FeelPage.hpp \
|
src/widgets/helper/NotebookTab.hpp \
|
||||||
src/util/InitUpdateButton.hpp \
|
src/widgets/helper/ResizingTextEdit.hpp \
|
||||||
src/widgets/dialogs/UpdateDialog.hpp \
|
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||||
src/widgets/settingspages/IgnoresPage.hpp \
|
src/widgets/helper/SearchPopup.hpp \
|
||||||
src/providers/twitch/PubsubClient.hpp \
|
src/widgets/helper/SettingsDialogTab.hpp \
|
||||||
src/providers/twitch/TwitchApi.hpp \
|
src/widgets/Notebook.hpp \
|
||||||
src/messages/Emote.hpp \
|
src/widgets/Scrollbar.hpp \
|
||||||
src/messages/ImageSet.hpp \
|
src/widgets/settingspages/AboutPage.hpp \
|
||||||
src/providers/bttv/BttvEmotes.hpp \
|
src/widgets/settingspages/AccountsPage.hpp \
|
||||||
src/providers/LinkResolver.hpp \
|
src/widgets/settingspages/AdvancedPage.hpp \
|
||||||
src/providers/ffz/FfzEmotes.hpp \
|
src/widgets/settingspages/BrowserExtensionPage.hpp \
|
||||||
src/autogenerated/ResourcesAutogen.hpp \
|
src/widgets/settingspages/CommandPage.hpp \
|
||||||
src/singletons/Badges.hpp \
|
src/widgets/settingspages/EmotesPage.hpp \
|
||||||
src/providers/twitch/TwitchBadges.hpp \
|
src/widgets/settingspages/ExternalToolsPage.hpp \
|
||||||
src/providers/chatterino/ChatterinoBadges.hpp \
|
src/widgets/settingspages/FeelPage.hpp \
|
||||||
src/common/Aliases.hpp \
|
src/widgets/settingspages/GeneralPage.hpp \
|
||||||
src/providers/twitch/TwitchParseCheerEmotes.hpp \
|
src/widgets/settingspages/HighlightingPage.hpp \
|
||||||
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
src/widgets/settingspages/IgnoresPage.hpp \
|
||||||
src/util/JsonQuery.hpp \
|
src/widgets/settingspages/KeyboardSettingsPage.hpp \
|
||||||
src/RunGui.hpp \
|
src/widgets/settingspages/LogsPage.hpp \
|
||||||
src/BrowserExtension.hpp \
|
src/widgets/settingspages/LookPage.hpp \
|
||||||
src/util/FormatTime.hpp \
|
src/widgets/settingspages/ModerationPage.hpp \
|
||||||
src/controllers/notifications/NotificationModel.hpp \
|
src/widgets/settingspages/NotificationPage.hpp \
|
||||||
src/singletons/Toasts.hpp \
|
src/widgets/settingspages/SettingsPage.hpp \
|
||||||
src/common/DownloadManager.hpp \
|
src/widgets/settingspages/SpecialChannelsPage.hpp \
|
||||||
src/messages/MessageContainer.hpp \
|
src/widgets/splits/ClosedSplits.hpp \
|
||||||
src/common/UsernameSet.hpp \
|
src/widgets/splits/Split.hpp \
|
||||||
src/widgets/settingspages/AdvancedPage.hpp \
|
src/widgets/splits/SplitContainer.hpp \
|
||||||
src/util/IncognitoBrowser.hpp \
|
src/widgets/splits/SplitHeader.hpp \
|
||||||
src/widgets/splits/ClosedSplits.hpp \
|
src/widgets/splits/SplitInput.hpp \
|
||||||
src/providers/ffz/FfzModBadge.hpp \
|
src/widgets/splits/SplitOverlay.hpp \
|
||||||
src/widgets/settingspages/GeneralPage.hpp \
|
src/widgets/StreamView.hpp \
|
||||||
src/messages/HistoricMessageAppearance.hpp \
|
src/widgets/Window.hpp \
|
||||||
src/providers/twitch/ChatroomChannel.hpp
|
src/controllers/pings/PingController.hpp \
|
||||||
|
src/controllers/pings/PingModel.hpp \
|
||||||
RESOURCES += \
|
|
||||||
resources/resources.qrc \
|
RESOURCES += \
|
||||||
resources/resources_autogenerated.qrc
|
resources/resources.qrc \
|
||||||
|
resources/resources_autogenerated.qrc
|
||||||
DISTFILES +=
|
|
||||||
|
DISTFILES +=
|
||||||
FORMS +=
|
|
||||||
|
FORMS +=
|
||||||
# do not use windows min/max macros
|
|
||||||
#win32 {
|
# do not use windows min/max macros
|
||||||
# DEFINES += NOMINMAX
|
#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.
|
# 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
|
# this will ignore a/b/c/d.txt and a/b/xd.txt
|
||||||
ignored_directories = ['__pycache__']
|
ignored_directories = ['__pycache__', 'linuxinstall']
|
||||||
|
|
||||||
def isNotIgnored(file):
|
def isNotIgnored(file):
|
||||||
# check if file exists in an ignored direcory
|
# check if file exists in an ignored direcory
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/"> <file>chatterino2.icns</file>
|
<qresource prefix="/"> <file>chatterino.icns</file>
|
||||||
<file>contributors.txt</file>
|
<file>contributors.txt</file>
|
||||||
<file>emoji.json</file>
|
<file>emoji.json</file>
|
||||||
<file>emojidata.txt</file>
|
<file>emojidata.txt</file>
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
<file>buttons/modModeEnabled.png</file>
|
<file>buttons/modModeEnabled.png</file>
|
||||||
<file>buttons/modModeEnabled2.png</file>
|
<file>buttons/modModeEnabled2.png</file>
|
||||||
<file>buttons/timeout.png</file>
|
<file>buttons/timeout.png</file>
|
||||||
|
<file>buttons/trashCan.png</file>
|
||||||
<file>buttons/unban.png</file>
|
<file>buttons/unban.png</file>
|
||||||
<file>buttons/unmod.png</file>
|
<file>buttons/unmod.png</file>
|
||||||
<file>buttons/update.png</file>
|
<file>buttons/update.png</file>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
#include "controllers/moderationactions/ModerationActions.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
|
#include "controllers/pings/PingController.hpp"
|
||||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
@ -53,6 +54,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
, commands(&this->emplace<CommandController>())
|
, commands(&this->emplace<CommandController>())
|
||||||
, highlights(&this->emplace<HighlightController>())
|
, highlights(&this->emplace<HighlightController>())
|
||||||
, notifications(&this->emplace<NotificationController>())
|
, notifications(&this->emplace<NotificationController>())
|
||||||
|
, pings(&this->emplace<PingController>())
|
||||||
, ignores(&this->emplace<IgnoreController>())
|
, ignores(&this->emplace<IgnoreController>())
|
||||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
, taggedUsers(&this->emplace<TaggedUsersController>())
|
||||||
, moderationActions(&this->emplace<ModerationActions>())
|
, moderationActions(&this->emplace<ModerationActions>())
|
||||||
|
@ -264,6 +266,7 @@ void Application::initPubsub()
|
||||||
auto msg = MessageBuilder(action).release();
|
auto msg = MessageBuilder(action).release();
|
||||||
|
|
||||||
postToThread([chan, msg] { chan->addMessage(msg); });
|
postToThread([chan, msg] { chan->addMessage(msg); });
|
||||||
|
chan->deleteMessage(msg->id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->start();
|
this->twitch.pubsub->start();
|
||||||
|
|
|
@ -18,6 +18,7 @@ class TaggedUsersController;
|
||||||
class AccountController;
|
class AccountController;
|
||||||
class ModerationActions;
|
class ModerationActions;
|
||||||
class NotificationController;
|
class NotificationController;
|
||||||
|
class PingController;
|
||||||
|
|
||||||
class Theme;
|
class Theme;
|
||||||
class WindowManager;
|
class WindowManager;
|
||||||
|
@ -62,6 +63,7 @@ public:
|
||||||
CommandController *const commands{};
|
CommandController *const commands{};
|
||||||
HighlightController *const highlights{};
|
HighlightController *const highlights{};
|
||||||
NotificationController *const notifications{};
|
NotificationController *const notifications{};
|
||||||
|
PingController *const pings{};
|
||||||
IgnoreController *const ignores{};
|
IgnoreController *const ignores{};
|
||||||
TaggedUsersController *const taggedUsers{};
|
TaggedUsersController *const taggedUsers{};
|
||||||
ModerationActions *const moderationActions{};
|
ModerationActions *const moderationActions{};
|
||||||
|
|
|
@ -18,6 +18,7 @@ Resources2::Resources2()
|
||||||
this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
|
this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
|
||||||
this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
|
this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
|
||||||
this->buttons.timeout = QPixmap(":/buttons/timeout.png");
|
this->buttons.timeout = QPixmap(":/buttons/timeout.png");
|
||||||
|
this->buttons.trashCan = QPixmap(":/buttons/trashCan.png");
|
||||||
this->buttons.unban = QPixmap(":/buttons/unban.png");
|
this->buttons.unban = QPixmap(":/buttons/unban.png");
|
||||||
this->buttons.unmod = QPixmap(":/buttons/unmod.png");
|
this->buttons.unmod = QPixmap(":/buttons/unmod.png");
|
||||||
this->buttons.update = QPixmap(":/buttons/update.png");
|
this->buttons.update = QPixmap(":/buttons/update.png");
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
QPixmap modModeEnabled;
|
QPixmap modModeEnabled;
|
||||||
QPixmap modModeEnabled2;
|
QPixmap modModeEnabled2;
|
||||||
QPixmap timeout;
|
QPixmap timeout;
|
||||||
|
QPixmap trashCan;
|
||||||
QPixmap unban;
|
QPixmap unban;
|
||||||
QPixmap unmod;
|
QPixmap unmod;
|
||||||
QPixmap update;
|
QPixmap update;
|
||||||
|
|
|
@ -78,7 +78,7 @@ void Channel::addMessage(MessagePtr message,
|
||||||
|
|
||||||
// FOURTF: change this when adding more providers
|
// FOURTF: change this when adding more providers
|
||||||
if (this->isTwitchChannel() &&
|
if (this->isTwitchChannel() &&
|
||||||
(!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
|
(!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog)))
|
||||||
{
|
{
|
||||||
app->logging->addMessage(this->name_, message);
|
app->logging->addMessage(this->name_, message);
|
||||||
}
|
}
|
||||||
|
@ -154,8 +154,9 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
||||||
for (int i = 0; i < snapshotLength; i++)
|
for (int i = 0; i < snapshotLength; i++)
|
||||||
{
|
{
|
||||||
auto &s = snapshot[i];
|
auto &s = snapshot[i];
|
||||||
if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) &&
|
if (s->loginName == message->timeoutUser &&
|
||||||
s->loginName == message->timeoutUser)
|
s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout,
|
||||||
|
MessageFlag::Whisper}))
|
||||||
{
|
{
|
||||||
// FOURTF: disabled for now
|
// FOURTF: disabled for now
|
||||||
// PAJLADA: Shitty solution described in Message.hpp
|
// PAJLADA: Shitty solution described in Message.hpp
|
||||||
|
@ -179,7 +180,8 @@ void Channel::disableAllMessages()
|
||||||
for (int i = 0; i < snapshotLength; i++)
|
for (int i = 0; i < snapshotLength; i++)
|
||||||
{
|
{
|
||||||
auto &message = snapshot[i];
|
auto &message = snapshot[i];
|
||||||
if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout}))
|
if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout,
|
||||||
|
MessageFlag::Whisper}))
|
||||||
{
|
{
|
||||||
continue;
|
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)
|
void Channel::addRecentChatter(const MessagePtr &message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -239,6 +260,11 @@ bool Channel::hasModRights() const
|
||||||
return this->isMod() || this->isBroadcaster();
|
return this->isMod() || this->isBroadcaster();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Channel::hasHighRateLimit() const
|
||||||
|
{
|
||||||
|
return this->isMod() || this->isBroadcaster();
|
||||||
|
}
|
||||||
|
|
||||||
bool Channel::isLive() const
|
bool Channel::isLive() const
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace chatterino {
|
||||||
|
|
||||||
struct Message;
|
struct Message;
|
||||||
using MessagePtr = std::shared_ptr<const Message>;
|
using MessagePtr = std::shared_ptr<const Message>;
|
||||||
enum class MessageFlag : uint16_t;
|
enum class MessageFlag : uint32_t;
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
class Channel : public std::enable_shared_from_this<Channel>
|
class Channel : public std::enable_shared_from_this<Channel>
|
||||||
|
@ -62,6 +62,7 @@ public:
|
||||||
void addOrReplaceTimeout(MessagePtr message);
|
void addOrReplaceTimeout(MessagePtr message);
|
||||||
void disableAllMessages();
|
void disableAllMessages();
|
||||||
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
||||||
|
void deleteMessage(QString messageID);
|
||||||
|
|
||||||
QStringList modList;
|
QStringList modList;
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ public:
|
||||||
virtual bool isMod() const;
|
virtual bool isMod() const;
|
||||||
virtual bool isBroadcaster() const;
|
virtual bool isBroadcaster() const;
|
||||||
virtual bool hasModRights() const;
|
virtual bool hasModRights() const;
|
||||||
|
virtual bool hasHighRateLimit() const;
|
||||||
virtual bool isLive() const;
|
virtual bool isLive() const;
|
||||||
virtual bool shouldIgnoreHighlights() const;
|
virtual bool shouldIgnoreHighlights() const;
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,7 @@ DownloadManager::~DownloadManager()
|
||||||
|
|
||||||
void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
||||||
{
|
{
|
||||||
QString filePath = fileURL;
|
|
||||||
QString saveFilePath;
|
QString saveFilePath;
|
||||||
QStringList filePathList = filePath.split('/');
|
|
||||||
saveFilePath =
|
saveFilePath =
|
||||||
getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png";
|
getPaths()->twitchProfileAvatars + "/twitch/" + channelName + ".png";
|
||||||
QNetworkRequest request;
|
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 &&
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
||||||
column < this->columnCount_);
|
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)
|
QStandardItem *getItem(int row, int column)
|
||||||
|
|
|
@ -59,7 +59,7 @@ void UsernameSet::insertPrefix(const QString &value)
|
||||||
{
|
{
|
||||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||||
|
|
||||||
if (string.isNull() || value < string)
|
if (string.isNull() || value.compare(string, Qt::CaseInsensitive) < 0)
|
||||||
string = value;
|
string = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +99,11 @@ bool Prefix::operator==(const Prefix &other) const
|
||||||
std::tie(other.first, other.second);
|
std::tie(other.first, other.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Prefix::operator!=(const Prefix &other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
bool Prefix::isStartOf(const QString &string) const
|
bool Prefix::isStartOf(const QString &string) const
|
||||||
{
|
{
|
||||||
if (string.size() == 0)
|
if (string.size() == 0)
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Prefix
|
class Prefix
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Prefix(const QString &string);
|
Prefix(const QString &string);
|
||||||
bool operator==(const Prefix &other) const;
|
bool operator==(const Prefix &other) const;
|
||||||
|
bool operator!=(const Prefix &other) const;
|
||||||
bool isStartOf(const QString &string) const;
|
bool isStartOf(const QString &string) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -19,9 +21,11 @@ private:
|
||||||
|
|
||||||
friend struct std::hash<Prefix>;
|
friend struct std::hash<Prefix>;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct hash<chatterino::Prefix> {
|
struct hash<chatterino::Prefix> {
|
||||||
size_t operator()(const chatterino::Prefix &prefix) const
|
size_t operator()(const chatterino::Prefix &prefix) const
|
||||||
|
@ -30,9 +34,18 @@ struct hash<chatterino::Prefix> {
|
||||||
size_t(prefix.second.unicode());
|
size_t(prefix.second.unicode());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct CaseInsensitiveLess {
|
||||||
|
bool operator()(const QString &lhs, const QString &rhs) const
|
||||||
|
{
|
||||||
|
return lhs.compare(rhs, Qt::CaseInsensitive) < 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class UsernameSet
|
class UsernameSet
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -66,7 +79,7 @@ public:
|
||||||
private:
|
private:
|
||||||
void insertPrefix(const QString &string);
|
void insertPrefix(const QString &string);
|
||||||
|
|
||||||
std::set<QString> items;
|
std::set<QString, CaseInsensitiveLess> items;
|
||||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
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)
|
if (this->categoryCount_[item->getCategory()]++ == 0)
|
||||||
{
|
{
|
||||||
auto row = this->createRow();
|
auto newRow = this->createRow();
|
||||||
|
|
||||||
setStringItem(row[0], item->getCategory(), false, false);
|
setStringItem(newRow[0], item->getCategory(), false, false);
|
||||||
row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
|
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;
|
return proposedIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/CombinePath.hpp"
|
#include "util/CombinePath.hpp"
|
||||||
#include "widgets/dialogs/LogsPopup.hpp"
|
#include "widgets/dialogs/LogsPopup.hpp"
|
||||||
|
|
||||||
|
@ -28,9 +29,143 @@
|
||||||
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
|
"/unban", "/timeout", "/untimeout", "/slow", "/slowoff", \
|
||||||
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
|
"/r9kbeta", "/r9kbetaoff", "/emoteonly", "/emoteonlyoff", \
|
||||||
"/clear", "/subscribers", "/subscribersoff", "/followers", \
|
"/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 {
|
namespace chatterino {
|
||||||
|
|
||||||
void CommandController::initialize(Settings &, Paths &paths)
|
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...
|
// works in a valid twitch channel and /whispers, etc...
|
||||||
if (!dryRun && channel->isTwitchChannel())
|
if (!dryRun && channel->isTwitchChannel())
|
||||||
{
|
{
|
||||||
if (commandName == "/w")
|
if (whisperCommands.contains(commandName, Qt::CaseInsensitive))
|
||||||
{
|
{
|
||||||
if (words.length() <= 2)
|
if (words.length() > 2)
|
||||||
{
|
{
|
||||||
return "";
|
appendWhisperMessageWordsLocally(words);
|
||||||
}
|
sendWhisperMessage(text);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
@ -413,13 +461,36 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
||||||
logs->show();
|
logs->show();
|
||||||
return "";
|
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);
|
auto maxSpaces = std::min(this->maxSpaces_, words.length() - 1);
|
||||||
|
@ -427,10 +498,10 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
||||||
{
|
{
|
||||||
commandName += ' ' + words[i + 1];
|
commandName += ' ' + words[i + 1];
|
||||||
|
|
||||||
auto it = this->commandsMap_.find(commandName);
|
const auto it = this->commandsMap_.find(commandName);
|
||||||
if (it != this->commandsMap_.end())
|
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,
|
QString CommandController::execCustomCommand(const QStringList &words,
|
||||||
const Command &command)
|
const Command &command,
|
||||||
|
bool dryRun)
|
||||||
{
|
{
|
||||||
QString result;
|
QString result;
|
||||||
|
|
||||||
|
@ -509,7 +581,17 @@ QString CommandController::execCustomCommand(const QStringList &words,
|
||||||
result = result.mid(1);
|
result = result.mid(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.replace("{{", "{");
|
auto res = result.replace("{{", "{");
|
||||||
|
|
||||||
|
if (dryRun || !appendWhisperMessageStringLocally(res))
|
||||||
|
{
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendWhisperMessage(res);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CommandController::getDefaultTwitchCommandList()
|
QStringList CommandController::getDefaultTwitchCommandList()
|
||||||
|
|
|
@ -49,7 +49,8 @@ private:
|
||||||
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
std::unique_ptr<pajlada::Settings::Setting<std::vector<Command>>>
|
||||||
commandsSetting_;
|
commandsSetting_;
|
||||||
|
|
||||||
QString execCustomCommand(const QStringList &words, const Command &command);
|
QString execCustomCommand(const QStringList &words, const Command &command,
|
||||||
|
bool dryRun);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -12,6 +12,8 @@ class Paths;
|
||||||
|
|
||||||
class IgnoreModel;
|
class IgnoreModel;
|
||||||
|
|
||||||
|
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
|
||||||
|
|
||||||
class IgnoreController final : public Singleton
|
class IgnoreController final : public Singleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -73,6 +73,10 @@ ModerationAction::ModerationAction(const QString &action)
|
||||||
{
|
{
|
||||||
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
|
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
|
||||||
}
|
}
|
||||||
|
else if (action.startsWith("/delete"))
|
||||||
|
{
|
||||||
|
this->image_ = Image::fromPixmap(getApp()->resources->buttons.trashCan);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
QString xD = action;
|
QString xD = action;
|
||||||
|
|
|
@ -103,16 +103,12 @@ void NotificationController::playSound()
|
||||||
static auto player = new QMediaPlayer;
|
static auto player = new QMediaPlayer;
|
||||||
static QUrl currentPlayerUrl;
|
static QUrl currentPlayerUrl;
|
||||||
|
|
||||||
QUrl highlightSoundUrl;
|
QUrl highlightSoundUrl =
|
||||||
if (getSettings()->notificationCustomSound)
|
getSettings()->notificationCustomSound
|
||||||
{
|
? QUrl::fromLocalFile(
|
||||||
highlightSoundUrl = QUrl::fromLocalFile(
|
getSettings()->notificationPathSound.getValue())
|
||||||
getSettings()->notificationPathSound.getValue());
|
: QUrl("qrc:/sounds/ping2.wav");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
|
|
||||||
}
|
|
||||||
if (currentPlayerUrl != highlightSoundUrl)
|
if (currentPlayerUrl != highlightSoundUrl)
|
||||||
{
|
{
|
||||||
player->setMedia(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
|
class LimitedQueue
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
typedef std::shared_ptr<std::vector<T>> Chunk;
|
using Chunk = std::vector<T>;
|
||||||
typedef std::shared_ptr<std::vector<Chunk>> ChunkVector;
|
using ChunkVector = std::vector<std::shared_ptr<Chunk>>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LimitedQueue(size_t limit = 1000)
|
LimitedQueue(size_t limit = 1000)
|
||||||
|
@ -42,9 +42,8 @@ public:
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
this->chunks_ =
|
this->chunks_ = std::make_shared<ChunkVector>();
|
||||||
std::make_shared<std::vector<std::shared_ptr<std::vector<T>>>>();
|
auto chunk = std::make_shared<Chunk>();
|
||||||
Chunk chunk = std::make_shared<std::vector<T>>();
|
|
||||||
chunk->resize(this->chunkSize_);
|
chunk->resize(this->chunkSize_);
|
||||||
this->chunks_->push_back(chunk);
|
this->chunks_->push_back(chunk);
|
||||||
this->firstChunkOffset_ = 0;
|
this->firstChunkOffset_ = 0;
|
||||||
|
@ -57,23 +56,21 @@ public:
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
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_)
|
if (lastChunk->size() <= this->lastChunkEnd_)
|
||||||
{
|
{
|
||||||
// create new chunk vector
|
// Last chunk is full, create a new one and rebuild our chunk vector
|
||||||
ChunkVector newVector = std::make_shared<
|
auto newVector = std::make_shared<ChunkVector>();
|
||||||
std::vector<std::shared_ptr<std::vector<T>>>>();
|
|
||||||
|
|
||||||
// copy chunks
|
// copy chunks
|
||||||
for (Chunk &chunk : *this->chunks_)
|
for (auto &chunk : *this->chunks_)
|
||||||
{
|
{
|
||||||
newVector->push_back(chunk);
|
newVector->push_back(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// push back new chunk
|
// push back new chunk
|
||||||
Chunk newChunk = std::make_shared<std::vector<T>>();
|
auto newChunk = std::make_shared<Chunk>();
|
||||||
newChunk->resize(this->chunkSize_);
|
newChunk->resize(this->chunkSize_);
|
||||||
newVector->push_back(newChunk);
|
newVector->push_back(newChunk);
|
||||||
|
|
||||||
|
@ -98,8 +95,7 @@ public:
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
// create new vector to clone chunks into
|
// create new vector to clone chunks into
|
||||||
ChunkVector newChunks = std::make_shared<
|
auto newChunks = std::make_shared<ChunkVector>();
|
||||||
std::vector<std::shared_ptr<std::vector<T>>>>();
|
|
||||||
|
|
||||||
newChunks->resize(this->chunks_->size());
|
newChunks->resize(this->chunks_->size());
|
||||||
|
|
||||||
|
@ -112,7 +108,7 @@ public:
|
||||||
// create new chunk for the first one
|
// create new chunk for the first one
|
||||||
size_t offset =
|
size_t offset =
|
||||||
std::min(this->space(), static_cast<qsizetype>(items.size()));
|
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);
|
newFirstChunk->resize(this->chunks_->front()->size() + offset);
|
||||||
|
|
||||||
for (size_t i = 0; i < offset; i++)
|
for (size_t i = 0; i < offset; i++)
|
||||||
|
@ -150,7 +146,7 @@ public:
|
||||||
|
|
||||||
for (size_t i = 0; i < this->chunks_->size(); i++)
|
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 start = i == 0 ? this->firstChunkOffset_ : 0;
|
||||||
size_t end =
|
size_t end =
|
||||||
|
@ -160,7 +156,7 @@ public:
|
||||||
{
|
{
|
||||||
if (chunk->at(j) == item)
|
if (chunk->at(j) == item)
|
||||||
{
|
{
|
||||||
Chunk newChunk = std::make_shared<std::vector<T>>();
|
auto newChunk = std::make_shared<Chunk>();
|
||||||
newChunk->resize(chunk->size());
|
newChunk->resize(chunk->size());
|
||||||
|
|
||||||
for (size_t k = 0; k < chunk->size(); k++)
|
for (size_t k = 0; k < chunk->size(); k++)
|
||||||
|
@ -189,7 +185,7 @@ public:
|
||||||
|
|
||||||
for (size_t i = 0; i < this->chunks_->size(); i++)
|
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 start = i == 0 ? this->firstChunkOffset_ : 0;
|
||||||
size_t end =
|
size_t end =
|
||||||
|
@ -199,7 +195,7 @@ public:
|
||||||
{
|
{
|
||||||
if (x == index)
|
if (x == index)
|
||||||
{
|
{
|
||||||
Chunk newChunk = std::make_shared<std::vector<T>>();
|
auto newChunk = std::make_shared<Chunk>();
|
||||||
newChunk->resize(chunk->size());
|
newChunk->resize(chunk->size());
|
||||||
|
|
||||||
for (size_t k = 0; k < chunk->size(); k++)
|
for (size_t k = 0; k < chunk->size(); k++)
|
||||||
|
@ -233,7 +229,7 @@ private:
|
||||||
qsizetype space()
|
qsizetype space()
|
||||||
{
|
{
|
||||||
size_t totalSize = 0;
|
size_t totalSize = 0;
|
||||||
for (Chunk &chunk : *this->chunks_)
|
for (auto &chunk : *this->chunks_)
|
||||||
{
|
{
|
||||||
totalSize += chunk->size();
|
totalSize += chunk->size();
|
||||||
}
|
}
|
||||||
|
@ -257,18 +253,15 @@ private:
|
||||||
|
|
||||||
deleted = this->chunks_->front()->at(this->firstChunkOffset_);
|
deleted = this->chunks_->front()->at(this->firstChunkOffset_);
|
||||||
|
|
||||||
this->firstChunkOffset_++;
|
|
||||||
|
|
||||||
// need to delete the first chunk
|
// need to delete the first chunk
|
||||||
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1)
|
if (this->firstChunkOffset_ == this->chunks_->front()->size() - 1)
|
||||||
{
|
{
|
||||||
// copy the chunk vector
|
// copy the chunk vector
|
||||||
ChunkVector newVector = std::make_shared<
|
auto newVector = std::make_shared<ChunkVector>();
|
||||||
std::vector<std::shared_ptr<std::vector<T>>>>();
|
|
||||||
|
|
||||||
// delete first chunk
|
// delete first chunk
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (Chunk &chunk : *this->chunks_)
|
for (auto &chunk : *this->chunks_)
|
||||||
{
|
{
|
||||||
if (!first)
|
if (!first)
|
||||||
{
|
{
|
||||||
|
@ -280,15 +273,20 @@ private:
|
||||||
this->chunks_ = newVector;
|
this->chunks_ = newVector;
|
||||||
this->firstChunkOffset_ = 0;
|
this->firstChunkOffset_ = 0;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->firstChunkOffset_++;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkVector chunks_;
|
std::shared_ptr<ChunkVector> chunks_;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
|
|
||||||
size_t firstChunkOffset_;
|
size_t firstChunkOffset_;
|
||||||
size_t lastChunkEnd_;
|
size_t lastChunkEnd_;
|
||||||
size_t limit_;
|
const size_t limit_;
|
||||||
|
|
||||||
const size_t chunkSize_ = 100;
|
const size_t chunkSize_ = 100;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
class MessageElement;
|
class MessageElement;
|
||||||
|
|
||||||
enum class MessageFlag : uint16_t {
|
enum class MessageFlag : uint32_t {
|
||||||
None = 0,
|
None = 0,
|
||||||
System = (1 << 0),
|
System = (1 << 0),
|
||||||
Timeout = (1 << 1),
|
Timeout = (1 << 1),
|
||||||
|
@ -30,6 +30,7 @@ enum class MessageFlag : uint16_t {
|
||||||
DoNotLog = (1 << 13),
|
DoNotLog = (1 << 13),
|
||||||
AutoMod = (1 << 14),
|
AutoMod = (1 << 14),
|
||||||
RecentMessage = (1 << 15),
|
RecentMessage = (1 << 15),
|
||||||
|
Whisper = (1 << 16)
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ struct Message : boost::noncopyable {
|
||||||
QTime parseTime;
|
QTime parseTime;
|
||||||
QString id;
|
QString id;
|
||||||
QString searchText;
|
QString searchText;
|
||||||
|
QString messageText;
|
||||||
QString loginName;
|
QString loginName;
|
||||||
QString displayName;
|
QString displayName;
|
||||||
QString localizedName;
|
QString localizedName;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
#include "providers/LinkResolver.hpp"
|
||||||
#include "providers/twitch/PubsubActions.hpp"
|
#include "providers/twitch/PubsubActions.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
|
@ -64,6 +65,8 @@ std::pair<MessagePtr, MessagePtr> makeAutomodMessage(
|
||||||
|
|
||||||
builder = MessageBuilder();
|
builder = MessageBuilder();
|
||||||
builder.emplace<TimestampElement>();
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TwitchModerationElement>();
|
||||||
|
builder.message().loginName = action.target.name;
|
||||||
builder.message().flags.set(MessageFlag::PubSub);
|
builder.message().flags.set(MessageFlag::PubSub);
|
||||||
|
|
||||||
builder
|
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::MessageBuilder(SystemMessageTag, const QString &text)
|
||||||
: MessageBuilder()
|
: MessageBuilder()
|
||||||
{
|
{
|
||||||
this->emplace<TimestampElement>();
|
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::System);
|
||||||
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
this->message().flags.set(MessageFlag::DoNotTriggerNotification);
|
||||||
|
this->message().messageText = text;
|
||||||
this->message().searchText = text;
|
this->message().searchText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +167,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||||
this->emplace<TimestampElement>();
|
this->emplace<TimestampElement>();
|
||||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||||
MessageColor::System);
|
MessageColor::System);
|
||||||
|
this->message().messageText = text;
|
||||||
this->message().searchText = text;
|
this->message().searchText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +224,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
|
||||||
|
|
||||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||||
MessageColor::System);
|
MessageColor::System);
|
||||||
|
this->message().messageText = text;
|
||||||
this->message().searchText = text;
|
this->message().searchText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,23 +237,15 @@ MessageBuilder::MessageBuilder(const UnbanAction &action)
|
||||||
|
|
||||||
this->message().timeoutUser = action.target.name;
|
this->message().timeoutUser = action.target.name;
|
||||||
|
|
||||||
QString text;
|
QString text =
|
||||||
|
QString("%1 %2 %3.")
|
||||||
if (action.wasBan())
|
.arg(action.source.name)
|
||||||
{
|
.arg(QString(action.wasBan() ? "unbanned" : "untimedout"))
|
||||||
text = QString("%1 unbanned %2.") //
|
.arg(action.target.name);
|
||||||
.arg(action.source.name)
|
|
||||||
.arg(action.target.name);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = QString("%1 untimedout %2.") //
|
|
||||||
.arg(action.source.name)
|
|
||||||
.arg(action.target.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
this->emplace<TextElement>(text, MessageElementFlag::Text,
|
||||||
MessageColor::System);
|
MessageColor::System);
|
||||||
|
this->message().messageText = text;
|
||||||
this->message().searchText = text;
|
this->message().searchText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,4 +356,57 @@ QString MessageBuilder::matchLink(const QString &string)
|
||||||
return captured;
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -38,7 +38,6 @@ class MessageBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MessageBuilder();
|
MessageBuilder();
|
||||||
MessageBuilder(const QString &text);
|
|
||||||
MessageBuilder(SystemMessageTag, const QString &text);
|
MessageBuilder(SystemMessageTag, const QString &text);
|
||||||
MessageBuilder(TimeoutMessageTag, const QString &username,
|
MessageBuilder(TimeoutMessageTag, const QString &username,
|
||||||
const QString &durationInSeconds, const QString &reason,
|
const QString &durationInSeconds, const QString &reason,
|
||||||
|
@ -54,6 +53,7 @@ public:
|
||||||
|
|
||||||
void append(std::unique_ptr<MessageElement> element);
|
void append(std::unique_ptr<MessageElement> element);
|
||||||
QString matchLink(const QString &string);
|
QString matchLink(const QString &string);
|
||||||
|
void addLink(const QString &origLink, const QString &matchedLink);
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
T *emplace(Args &&... args)
|
T *emplace(Args &&... args)
|
||||||
|
|
|
@ -138,10 +138,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||||
if (image->isEmpty())
|
if (image->isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto emoteScale =
|
auto emoteScale = getSettings()->emoteScale.getValue();
|
||||||
this->getFlags().hasAny(MessageElementFlag::Badges)
|
|
||||||
? 1
|
|
||||||
: getSettings()->emoteScale.getValue();
|
|
||||||
|
|
||||||
auto size =
|
auto size =
|
||||||
QSize(int(container.getScale() * image->width() * emoteScale),
|
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
|
// TEXT
|
||||||
TextElement::TextElement(const QString &text, MessageElementFlags flags,
|
TextElement::TextElement(const QString &text, MessageElementFlags flags,
|
||||||
const MessageColor &color, FontStyle style)
|
const MessageColor &color, FontStyle style)
|
||||||
|
@ -188,7 +210,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
for (Word &word : this->words_)
|
for (Word &word : this->words_)
|
||||||
{
|
{
|
||||||
auto getTextLayoutElement = [&](QString text, int width,
|
auto getTextLayoutElement = [&](QString text, int width,
|
||||||
bool trailingSpace) {
|
bool hasTrailingSpace) {
|
||||||
auto color = this->color_.getColor(*app->themes);
|
auto color = this->color_.getColor(*app->themes);
|
||||||
app->themes->normalizeColor(color);
|
app->themes->normalizeColor(color);
|
||||||
|
|
||||||
|
@ -196,7 +218,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
*this, text, QSize(width, metrics.height()),
|
*this, text, QSize(width, metrics.height()),
|
||||||
color, this->style_, container.getScale()))
|
color, this->style_, container.getScale()))
|
||||||
->setLink(this->getLink());
|
->setLink(this->getLink());
|
||||||
e->setTrailingSpace(trailingSpace);
|
e->setTrailingSpace(hasTrailingSpace);
|
||||||
e->setText(text);
|
e->setText(text);
|
||||||
|
|
||||||
// If URL link was changed,
|
// If URL link was changed,
|
||||||
|
@ -291,7 +313,6 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
|
||||||
{
|
{
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
if (getSettings()->timestampFormat != this->format_)
|
if (getSettings()->timestampFormat != this->format_)
|
||||||
{
|
{
|
||||||
this->format_ = getSettings()->timestampFormat.getValue();
|
this->format_ = getSettings()->timestampFormat.getValue();
|
||||||
|
|
|
@ -42,6 +42,7 @@ enum class MessageElementFlag {
|
||||||
FfzEmoteText = (1 << 11),
|
FfzEmoteText = (1 << 11),
|
||||||
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
||||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
|
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
|
||||||
|
EmoteText = TwitchEmoteText | BttvEmoteText | FfzEmoteText,
|
||||||
|
|
||||||
BitsStatic = (1 << 12),
|
BitsStatic = (1 << 12),
|
||||||
BitsAnimated = (1 << 13),
|
BitsAnimated = (1 << 13),
|
||||||
|
@ -74,9 +75,6 @@ enum class MessageElementFlag {
|
||||||
// - Chatterino top donator badge
|
// - Chatterino top donator badge
|
||||||
BadgeChatterino = (1 << 18),
|
BadgeChatterino = (1 << 18),
|
||||||
|
|
||||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke)
|
|
||||||
// custom badge?
|
|
||||||
|
|
||||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription |
|
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription |
|
||||||
BadgeVanity | BadgeChatterino,
|
BadgeVanity | BadgeChatterino,
|
||||||
|
|
||||||
|
@ -216,6 +214,18 @@ private:
|
||||||
EmotePtr emote_;
|
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
|
// contains a text, formated depending on the preferences
|
||||||
class TimestampElement : public MessageElement
|
class TimestampElement : public MessageElement
|
||||||
{
|
{
|
||||||
|
|
|
@ -99,17 +99,14 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
|
void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
{
|
{
|
||||||
this->layoutCount_++;
|
this->layoutCount_++;
|
||||||
|
|
||||||
const auto addTest = this->message_->flags.hasAny(
|
|
||||||
{MessageFlag::DisconnectedMessage, MessageFlag::ConnectedMessage});
|
|
||||||
|
|
||||||
auto messageFlags = this->message_->flags;
|
auto messageFlags = this->message_->flags;
|
||||||
|
|
||||||
if (this->flags.has(MessageLayoutFlag::Expanded) ||
|
if (this->flags.has(MessageLayoutFlag::Expanded) ||
|
||||||
(_flags.has(MessageElementFlag::ModeratorTools) &&
|
(flags.has(MessageElementFlag::ModeratorTools) &&
|
||||||
!this->message_->flags.has(MessageFlag::Disabled))) //
|
!this->message_->flags.has(MessageFlag::Disabled))) //
|
||||||
{
|
{
|
||||||
messageFlags.unset(MessageFlag::Collapsed);
|
messageFlags.unset(MessageFlag::Collapsed);
|
||||||
|
@ -117,25 +114,21 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
|
||||||
|
|
||||||
this->container_->begin(width, this->scale_, messageFlags);
|
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)
|
for (const auto &element : this->message_->elements)
|
||||||
{
|
{
|
||||||
element->addToContainer(*this->container_, _flags);
|
if (getSettings()->hideModerated &&
|
||||||
}
|
this->message_->flags.has(MessageFlag::Disabled))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (addTest)
|
if (getSettings()->hideModerationActions &&
|
||||||
{
|
this->message_->flags.has(MessageFlag::Timeout))
|
||||||
this->container_->breakLine();
|
{
|
||||||
this->container_->addElement(new TestLayoutElement(
|
continue;
|
||||||
EmptyElement::instance(), QSize(width, this->scale_ * 6),
|
}
|
||||||
getTheme()->messages.backgrounds.regular, true));
|
|
||||||
|
element->addToContainer(*this->container_, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->height_ != this->container_->getHeight())
|
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);
|
app->themes->messages.disabled);
|
||||||
// painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
// painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
||||||
// QBrush(QColor(64, 64, 64, 64)));
|
// 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))
|
if (this->message_->flags.has(MessageFlag::RecentMessage))
|
||||||
{
|
{
|
||||||
const auto &historicMessageAppearance =
|
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
||||||
getSettings()->historicMessagesAppearance.getValue();
|
app->themes->messages.disabled);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw selection
|
// draw selection
|
||||||
|
|
|
@ -157,7 +157,7 @@ void MessageLayoutContainer::breakLine()
|
||||||
int yExtra = 0;
|
int yExtra = 0;
|
||||||
if (isCompactEmote)
|
if (isCompactEmote)
|
||||||
{
|
{
|
||||||
// yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
|
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (element->getCreator().getFlags() &
|
// if (element->getCreator().getFlags() &
|
||||||
|
@ -390,17 +390,17 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
int lineIndex2 = lineIndex + 1;
|
int lineIndex2 = lineIndex + 1;
|
||||||
for (; lineIndex2 < this->lines_.size(); lineIndex2++)
|
for (; lineIndex2 < this->lines_.size(); lineIndex2++)
|
||||||
{
|
{
|
||||||
Line &line = this->lines_[lineIndex2];
|
Line &line2 = this->lines_[lineIndex2];
|
||||||
QRect rect = line.rect;
|
QRect rect = line2.rect;
|
||||||
|
|
||||||
rect.setTop(std::max(0, rect.top()) + yOffset);
|
rect.setTop(std::max(0, rect.top()) + yOffset);
|
||||||
rect.setBottom(
|
rect.setBottom(
|
||||||
std::min(this->height_, rect.bottom()) +
|
std::min(this->height_, rect.bottom()) +
|
||||||
yOffset);
|
yOffset);
|
||||||
rect.setLeft(this->elements_[line.startIndex]
|
rect.setLeft(this->elements_[line2.startIndex]
|
||||||
->getRect()
|
->getRect()
|
||||||
.left());
|
.left());
|
||||||
rect.setRight(this->elements_[line.endIndex - 1]
|
rect.setRight(this->elements_[line2.endIndex - 1]
|
||||||
->getRect()
|
->getRect()
|
||||||
.right());
|
.right());
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class QPainter;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
enum class MessageFlag : uint16_t;
|
enum class MessageFlag : uint32_t;
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
struct Margin {
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -41,6 +41,7 @@ public:
|
||||||
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
||||||
virtual int getMouseOverIndex(const QPoint &abs) const = 0;
|
virtual int getMouseOverIndex(const QPoint &abs) const = 0;
|
||||||
virtual int getXFromIndex(int index) = 0;
|
virtual int getXFromIndex(int index) = 0;
|
||||||
|
|
||||||
const Link &getLink() const;
|
const Link &getLink() const;
|
||||||
const QString &getText() const;
|
const QString &getText() const;
|
||||||
FlagsEnum<MessageElementFlag> getFlags() const;
|
FlagsEnum<MessageElementFlag> getFlags() const;
|
||||||
|
@ -125,25 +126,4 @@ private:
|
||||||
QString line2;
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "providers/LinkResolver.hpp"
|
#include "providers/LinkResolver.hpp"
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "messages/Link.hpp"
|
#include "messages/Link.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
@ -12,12 +13,15 @@ namespace chatterino {
|
||||||
void LinkResolver::getLinkInfo(
|
void LinkResolver::getLinkInfo(
|
||||||
const QString url, std::function<void(QString, Link)> successCallback)
|
const QString url, std::function<void(QString, Link)> successCallback)
|
||||||
{
|
{
|
||||||
QString requestUrl("https://braize.pajlada.com/chatterino/link_resolver/" +
|
if (!getSettings()->linkInfoTooltip)
|
||||||
QUrl::toPercentEncoding(url, "", "/:"));
|
{
|
||||||
|
successCallback("No link info loaded", Link(Link::Url, url));
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Uncomment to test crashes
|
// Uncomment to test crashes
|
||||||
// QTimer::singleShot(3000, [=]() {
|
// QTimer::singleShot(3000, [=]() {
|
||||||
NetworkRequest request(requestUrl);
|
NetworkRequest request(Env::get().linkResolverUrl.arg(
|
||||||
|
QString::fromUtf8(QUrl::toPercentEncoding(url, "", "/:"))));
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
request.setTimeout(30000);
|
request.setTimeout(30000);
|
||||||
request.onSuccess([successCallback, url](auto result) mutable -> Outcome {
|
request.onSuccess([successCallback, url](auto result) mutable -> Outcome {
|
||||||
|
|
|
@ -36,9 +36,9 @@ namespace {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const auto &shortCodes = unparsedEmoji["short_names"];
|
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()
|
void Emojis::loadEmojiSet()
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
getSettings()->emojiSet.connect([=](const auto &emojiSet) {
|
getSettings()->emojiSet.connect([=](const auto &emojiSet) {
|
||||||
this->emojis.each([=](const auto &name,
|
this->emojis.each([=](const auto &name,
|
||||||
std::shared_ptr<EmojiData> &emoji) {
|
std::shared_ptr<EmojiData> &emoji) {
|
||||||
|
|
|
@ -231,9 +231,8 @@ void AbstractIrcServer::onConnected()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->channelMutex);
|
std::lock_guard<std::mutex> lock(this->channelMutex);
|
||||||
|
|
||||||
auto connected = makeSystemMessage("connected");
|
auto connectedMsg = makeSystemMessage("connected");
|
||||||
connected->flags.set(MessageFlag::ConnectedMessage);
|
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
|
||||||
connected->flags.set(MessageFlag::Centered);
|
|
||||||
auto reconnected = makeSystemMessage("reconnected");
|
auto reconnected = makeSystemMessage("reconnected");
|
||||||
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
reconnected->flags.set(MessageFlag::ConnectedMessage);
|
||||||
|
|
||||||
|
@ -257,7 +256,7 @@ void AbstractIrcServer::onConnected()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
chan->addMessage(connected);
|
chan->addMessage(connectedMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->falloffCounter_ = 1;
|
this->falloffCounter_ = 1;
|
||||||
|
@ -269,7 +268,7 @@ void AbstractIrcServer::onDisconnected()
|
||||||
|
|
||||||
MessageBuilder b(systemMessage, "disconnected");
|
MessageBuilder b(systemMessage, "disconnected");
|
||||||
b->flags.set(MessageFlag::DisconnectedMessage);
|
b->flags.set(MessageFlag::DisconnectedMessage);
|
||||||
auto disconnected = b.release();
|
auto disconnectedMsg = b.release();
|
||||||
|
|
||||||
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
for (std::weak_ptr<Channel> &weak : this->channels.values())
|
||||||
{
|
{
|
||||||
|
@ -279,7 +278,7 @@ void AbstractIrcServer::onDisconnected()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
chan->addMessage(disconnected);
|
chan->addMessage(disconnectedMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,69 @@
|
||||||
|
|
||||||
namespace chatterino {
|
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()
|
IrcMessageHandler &IrcMessageHandler::getInstance()
|
||||||
{
|
{
|
||||||
static IrcMessageHandler instance;
|
static IrcMessageHandler instance;
|
||||||
return 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,
|
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
TwitchServer &server)
|
TwitchServer &server)
|
||||||
{
|
{
|
||||||
|
@ -203,28 +260,78 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
// refresh all
|
// refresh all
|
||||||
app->windows->repaintVisibleChatWidgets(chan.get());
|
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)
|
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())
|
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());
|
TwitchChannel *tc = dynamic_cast<TwitchChannel *>(c.get());
|
||||||
if (tc != nullptr)
|
if (tc != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -248,6 +355,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
if (!builder.isIgnored())
|
if (!builder.isIgnored())
|
||||||
{
|
{
|
||||||
|
builder->flags.set(MessageFlag::Whisper);
|
||||||
MessagePtr _message = builder.build();
|
MessagePtr _message = builder.build();
|
||||||
|
|
||||||
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
|
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,
|
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
TwitchServer &server)
|
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)
|
void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
MessagePtr msg = makeSystemMessage(message->content());
|
auto builtMessages = this->parseNoticeMessage(message);
|
||||||
|
|
||||||
QString channelName;
|
for (auto msg : builtMessages)
|
||||||
if (!trimChannelName(message->target(), channelName))
|
|
||||||
{
|
{
|
||||||
// Notice wasn't targeted at a single channel, send to all twitch
|
QString channelName;
|
||||||
// channels
|
if (!trimChannelName(message->target(), channelName) ||
|
||||||
app->twitch.server->forEachChannelAndSpecialChannels(
|
channelName == "jtv")
|
||||||
[msg](const auto &c) {
|
{
|
||||||
c->addMessage(msg); //
|
// 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(
|
void IrcMessageHandler::handleWriteConnectionNoticeMessage(
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
|
#include "messages/Message.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchServer;
|
class TwitchServer;
|
||||||
|
class Channel;
|
||||||
|
|
||||||
class IrcMessageHandler
|
class IrcMessageHandler
|
||||||
{
|
{
|
||||||
|
@ -13,17 +15,37 @@ class IrcMessageHandler
|
||||||
public:
|
public:
|
||||||
static IrcMessageHandler &getInstance();
|
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,
|
void handlePrivMessage(Communi::IrcPrivateMessage *message,
|
||||||
TwitchServer &server);
|
TwitchServer &server);
|
||||||
|
|
||||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||||
|
void handleClearMessageMessage(Communi::IrcMessage *message);
|
||||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||||
void handleWhisperMessage(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,
|
void handleUserNoticeMessage(Communi::IrcMessage *message,
|
||||||
TwitchServer &server);
|
TwitchServer &server);
|
||||||
|
|
||||||
void handleModeMessage(Communi::IrcMessage *message);
|
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 handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||||
|
|
||||||
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||||
|
|
||||||
void handleJoinMessage(Communi::IrcMessage *message);
|
void handleJoinMessage(Communi::IrcMessage *message);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
|
@ -534,9 +535,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest req(
|
NetworkRequest req(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key));
|
||||||
"https://braize.pajlada.com/chatterino/twitchemotes/set/" +
|
|
||||||
emoteSet->key + "/");
|
|
||||||
req.setUseQuickLoadCache(true);
|
req.setUseQuickLoadCache(true);
|
||||||
|
|
||||||
req.onError([](int errorCode) -> bool {
|
req.onError([](int errorCode) -> bool {
|
||||||
|
|
|
@ -172,8 +172,6 @@ bool TwitchAccountManager::isLoggedIn() const
|
||||||
|
|
||||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||||
{
|
{
|
||||||
const auto &accs = this->accounts.getVector();
|
|
||||||
|
|
||||||
auto userID(account->getUserId());
|
auto userID(account->getUserId());
|
||||||
if (!userID.isEmpty())
|
if (!userID.isEmpty())
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
|
@ -9,6 +10,7 @@
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
||||||
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "providers/twitch/PubsubClient.hpp"
|
#include "providers/twitch/PubsubClient.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
|
@ -29,10 +31,14 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
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)
|
auto parseRecentMessages(const QJsonObject &jsonRoot, ChannelPtr channel)
|
||||||
{
|
{
|
||||||
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
|
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
|
||||||
std::vector<MessagePtr> messages;
|
std::vector<Communi::IrcMessage *> messages;
|
||||||
|
|
||||||
if (jsonMessages.empty())
|
if (jsonMessages.empty())
|
||||||
return messages;
|
return messages;
|
||||||
|
@ -40,26 +46,17 @@ namespace {
|
||||||
for (const auto jsonMessage : jsonMessages)
|
for (const auto jsonMessage : jsonMessages)
|
||||||
{
|
{
|
||||||
auto content = jsonMessage.toString().toUtf8();
|
auto content = jsonMessage.toString().toUtf8();
|
||||||
// passing nullptr as the channel makes the message invalid but we
|
messages.emplace_back(
|
||||||
// don't check for that anyways
|
Communi::IrcMessage::fromData(content, nullptr));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
|
std::pair<Outcome, UsernameSet> parseChatters(const QJsonObject &jsonRoot)
|
||||||
{
|
{
|
||||||
static QStringList categories = {"moderators", "staff", "admins",
|
static QStringList categories = {"broadcaster", "vips", "moderators",
|
||||||
"global_mods", "viewers"};
|
"staff", "admins", "global_mods",
|
||||||
|
"viewers"};
|
||||||
|
|
||||||
auto usernames = UsernameSet();
|
auto usernames = UsernameSet();
|
||||||
|
|
||||||
|
@ -127,10 +124,6 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
[=] { this->refreshLiveStatus(); });
|
[=] { this->refreshLiveStatus(); });
|
||||||
this->liveStatusTimer_.start(60 * 1000);
|
this->liveStatusTimer_.start(60 * 1000);
|
||||||
|
|
||||||
// --
|
|
||||||
this->messageSuffix_.append(' ');
|
|
||||||
this->messageSuffix_.append(QChar(0x206D));
|
|
||||||
|
|
||||||
// debugging
|
// debugging
|
||||||
#if 0
|
#if 0
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
@ -199,13 +192,13 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->hasModRights())
|
if (!this->hasHighRateLimit())
|
||||||
{
|
{
|
||||||
if (getSettings()->allowDuplicateMessages)
|
if (getSettings()->allowDuplicateMessages)
|
||||||
{
|
{
|
||||||
if (parsedMessage == this->lastSentMessage_)
|
if (parsedMessage == this->lastSentMessage_)
|
||||||
{
|
{
|
||||||
parsedMessage.append(this->messageSuffix_);
|
parsedMessage.append(MAGIC_MESSAGE_SUFFIX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +218,16 @@ bool TwitchChannel::isMod() const
|
||||||
return this->mod_;
|
return this->mod_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TwitchChannel::isVIP() const
|
||||||
|
{
|
||||||
|
return this->vip_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TwitchChannel::isStaff() const
|
||||||
|
{
|
||||||
|
return this->staff_;
|
||||||
|
}
|
||||||
|
|
||||||
void TwitchChannel::setMod(bool value)
|
void TwitchChannel::setMod(bool value)
|
||||||
{
|
{
|
||||||
if (this->mod_ != 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
|
bool TwitchChannel::isBroadcaster() const
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
@ -242,6 +265,11 @@ bool TwitchChannel::isBroadcaster() const
|
||||||
return this->getName() == app->accounts->twitch.getCurrent()->getUserName();
|
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)
|
void TwitchChannel::addRecentChatter(const MessagePtr &message)
|
||||||
{
|
{
|
||||||
this->chatters_.access()->insert(message->displayName);
|
this->chatters_.access()->insert(message->displayName);
|
||||||
|
@ -445,7 +473,7 @@ void TwitchChannel::setLive(bool newLiveStatus)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto offline =
|
auto offline =
|
||||||
makeSystemMessage(this->getName() + " is offline");
|
makeSystemMessage(this->getDisplayName() + " is offline");
|
||||||
this->addMessage(offline);
|
this->addMessage(offline);
|
||||||
}
|
}
|
||||||
guard->live = newLiveStatus;
|
guard->live = newLiveStatus;
|
||||||
|
@ -575,12 +603,13 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
|
|
||||||
void TwitchChannel::loadRecentMessages()
|
void TwitchChannel::loadRecentMessages()
|
||||||
{
|
{
|
||||||
static QString genericURL =
|
if (!getSettings()->loadTwitchMessageHistoryOnConnect)
|
||||||
"https://tmi.twitch.tv/api/rooms/%1/recent_messages?client_id=" +
|
{
|
||||||
getDefaultClientID();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRequest request(genericURL.arg(this->roomId()));
|
NetworkRequest request(
|
||||||
request.makeAuthorizedV5(getDefaultClientID());
|
Env::get().recentMessagesApiUrl.arg(this->getName()));
|
||||||
request.setCaller(QThread::currentThread());
|
request.setCaller(QThread::currentThread());
|
||||||
// can't be concurrent right now due to SignalVector
|
// can't be concurrent right now due to SignalVector
|
||||||
// request.setExecuteConcurrently(true);
|
// request.setExecuteConcurrently(true);
|
||||||
|
@ -592,7 +621,21 @@ void TwitchChannel::loadRecentMessages()
|
||||||
|
|
||||||
auto messages = parseRecentMessages(result.parseJson(), shared);
|
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;
|
return Success;
|
||||||
});
|
});
|
||||||
|
@ -618,11 +661,11 @@ void TwitchChannel::refreshChatters()
|
||||||
{
|
{
|
||||||
// setting?
|
// setting?
|
||||||
const auto streamStatus = this->accessStreamStatus();
|
const auto streamStatus = this->accessStreamStatus();
|
||||||
|
const auto viewerCount = static_cast<int>(streamStatus->viewerCount);
|
||||||
if (getSettings()->onlyFetchChattersForSmallerStreamers)
|
if (getSettings()->onlyFetchChattersForSmallerStreamers)
|
||||||
{
|
{
|
||||||
if (streamStatus->live &&
|
if (streamStatus->live &&
|
||||||
streamStatus->viewerCount > getSettings()->smallStreamerLimit)
|
viewerCount > getSettings()->smallStreamerLimit)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -674,8 +717,8 @@ void TwitchChannel::refreshBadges()
|
||||||
{
|
{
|
||||||
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
|
auto &versions = (*badgeSets)[jsonBadgeSet.key()];
|
||||||
|
|
||||||
auto _ = jsonBadgeSet->toObject()["versions"].toObject();
|
auto _set = jsonBadgeSet->toObject()["versions"].toObject();
|
||||||
for (auto jsonVersion_ = _.begin(); jsonVersion_ != _.end();
|
for (auto jsonVersion_ = _set.begin(); jsonVersion_ != _set.end();
|
||||||
jsonVersion_++)
|
jsonVersion_++)
|
||||||
{
|
{
|
||||||
auto jsonVersion = jsonVersion_->toObject();
|
auto jsonVersion = jsonVersion_->toObject();
|
||||||
|
|
|
@ -61,7 +61,10 @@ public:
|
||||||
virtual bool canSendMessage() const override;
|
virtual bool canSendMessage() const override;
|
||||||
virtual void sendMessage(const QString &message) override;
|
virtual void sendMessage(const QString &message) override;
|
||||||
virtual bool isMod() const override;
|
virtual bool isMod() const override;
|
||||||
|
bool isVIP() const;
|
||||||
|
bool isStaff() const;
|
||||||
virtual bool isBroadcaster() const override;
|
virtual bool isBroadcaster() const override;
|
||||||
|
virtual bool hasHighRateLimit() const override;
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const QString &subscriptionUrl();
|
const QString &subscriptionUrl();
|
||||||
|
@ -123,6 +126,8 @@ private:
|
||||||
void addPartedUser(const QString &user);
|
void addPartedUser(const QString &user);
|
||||||
void setLive(bool newLiveStatus);
|
void setLive(bool newLiveStatus);
|
||||||
void setMod(bool value);
|
void setMod(bool value);
|
||||||
|
void setVIP(bool value);
|
||||||
|
void setStaff(bool value);
|
||||||
void setRoomId(const QString &id);
|
void setRoomId(const QString &id);
|
||||||
void setRoomModes(const RoomModes &roomModes_);
|
void setRoomModes(const RoomModes &roomModes_);
|
||||||
|
|
||||||
|
@ -151,6 +156,8 @@ private:
|
||||||
FfzModBadge ffzCustomModBadge_;
|
FfzModBadge ffzCustomModBadge_;
|
||||||
|
|
||||||
bool mod_ = false;
|
bool mod_ = false;
|
||||||
|
bool vip_ = false;
|
||||||
|
bool staff_ = false;
|
||||||
UniqueAccess<QString> roomID_;
|
UniqueAccess<QString> roomID_;
|
||||||
|
|
||||||
UniqueAccess<QStringList> joinedUsers_;
|
UniqueAccess<QStringList> joinedUsers_;
|
||||||
|
@ -159,7 +166,6 @@ private:
|
||||||
bool partedUsersMergeQueued_ = false;
|
bool partedUsersMergeQueued_ = false;
|
||||||
|
|
||||||
// --
|
// --
|
||||||
QByteArray messageSuffix_;
|
|
||||||
QString lastSentMessage_;
|
QString lastSentMessage_;
|
||||||
QObject lifetimeGuard_;
|
QObject lifetimeGuard_;
|
||||||
QTimer liveStatusTimer_;
|
QTimer liveStatusTimer_;
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
|
#include "controllers/pings/PingController.hpp"
|
||||||
#include "debug/Log.hpp"
|
#include "debug/Log.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/LinkResolver.hpp"
|
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
@ -80,6 +80,19 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||||
{
|
{
|
||||||
if (sourceUserID == user.id)
|
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 {}",
|
log("Blocking message because it's from blocked user {}",
|
||||||
user.name);
|
user.name);
|
||||||
return true;
|
return true;
|
||||||
|
@ -109,12 +122,24 @@ MessagePtr TwitchMessageBuilder::build()
|
||||||
|
|
||||||
this->appendChannelName();
|
this->appendChannelName();
|
||||||
|
|
||||||
|
if (this->tags.contains("rm-deleted"))
|
||||||
|
{
|
||||||
|
this->message().flags.set(MessageFlag::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
bool isPastMsg = this->tags.contains("historical");
|
bool isPastMsg = this->tags.contains("historical");
|
||||||
if (isPastMsg)
|
if (isPastMsg)
|
||||||
{
|
{
|
||||||
// This may be architecture dependent(datatype)
|
// 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);
|
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
|
||||||
this->emplace<TimestampElement>(dateTime.time());
|
this->emplace<TimestampElement>(dateTime.time());
|
||||||
}
|
}
|
||||||
|
@ -310,13 +335,13 @@ MessagePtr TwitchMessageBuilder::build()
|
||||||
QRegularExpression emoteregex(
|
QRegularExpression emoteregex(
|
||||||
"\\b" + std::get<2>(tup).string + "\\b",
|
"\\b" + std::get<2>(tup).string + "\\b",
|
||||||
QRegularExpression::UseUnicodePropertiesOption);
|
QRegularExpression::UseUnicodePropertiesOption);
|
||||||
auto match = emoteregex.match(midExtendedRef);
|
auto _match = emoteregex.match(midExtendedRef);
|
||||||
if (match.hasMatch())
|
if (_match.hasMatch())
|
||||||
{
|
{
|
||||||
int last = match.lastCapturedIndex();
|
int last = _match.lastCapturedIndex();
|
||||||
for (int i = 0; i <= last; ++i)
|
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));
|
twitchEmotes.push_back(std::move(tup));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,7 +439,9 @@ MessagePtr TwitchMessageBuilder::build()
|
||||||
|
|
||||||
this->addWords(splits, twitchEmotes);
|
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();
|
return this->release();
|
||||||
}
|
}
|
||||||
|
@ -512,56 +539,7 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static QRegularExpression domainRegex(
|
this->addLink(string, linkString);
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!linkString.isEmpty()) {
|
// if (!linkString.isEmpty()) {
|
||||||
|
@ -605,7 +583,7 @@ void TwitchMessageBuilder::parseMessageID()
|
||||||
|
|
||||||
if (iterator != this->tags.end())
|
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()
|
void TwitchMessageBuilder::appendChannelName()
|
||||||
{
|
{
|
||||||
QString channelName("#" + this->channel->getName());
|
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,
|
this->emplace<TextElement>(channelName, MessageElementFlag::ChannelName,
|
||||||
MessageColor::System) //
|
MessageColor::System) //
|
||||||
|
@ -805,16 +783,10 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the media player url if necessary
|
// update the media player url if necessary
|
||||||
QUrl highlightSoundUrl;
|
QUrl highlightSoundUrl =
|
||||||
if (getSettings()->customHighlightSound)
|
getSettings()->customHighlightSound
|
||||||
{
|
? QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue())
|
||||||
highlightSoundUrl =
|
: QUrl("qrc:/sounds/ping2.wav");
|
||||||
QUrl::fromLocalFile(getSettings()->pathHighlightSound.getValue());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
highlightSoundUrl = QUrl("qrc:/sounds/ping2.wav");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPlayerUrl != highlightSoundUrl)
|
if (currentPlayerUrl != highlightSoundUrl)
|
||||||
{
|
{
|
||||||
|
@ -916,13 +888,16 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg)
|
||||||
|
|
||||||
if (!isPastMsg)
|
if (!isPastMsg)
|
||||||
{
|
{
|
||||||
if (playSound &&
|
bool notMuted = !getApp()->pings->isMuted(this->channel->getName());
|
||||||
(!hasFocus || getSettings()->highlightAlwaysPlaySound))
|
bool resolveFocus =
|
||||||
|
!hasFocus || getSettings()->highlightAlwaysPlaySound;
|
||||||
|
|
||||||
|
if (playSound && notMuted && resolveFocus)
|
||||||
{
|
{
|
||||||
player->play();
|
player->play();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doAlert)
|
if (doAlert && notMuted)
|
||||||
{
|
{
|
||||||
getApp()->windows->sendAlert();
|
getApp()->windows->sendAlert();
|
||||||
}
|
}
|
||||||
|
@ -983,52 +958,36 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
||||||
|
|
||||||
Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
Outcome TwitchMessageBuilder::tryAppendEmote(const EmoteName &name)
|
||||||
{
|
{
|
||||||
// Special channels, like /whispers and /channels return here
|
auto *app = getApp();
|
||||||
// This means they will not render any BTTV or FFZ emotes
|
|
||||||
if (this->twitchChannel == nullptr)
|
const auto &globalBttvEmotes = app->twitch.server->getBttvEmotes();
|
||||||
{
|
const auto &globalFfzEmotes = app->twitch.server->getFfzEmotes();
|
||||||
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 flags = MessageElementFlags();
|
auto flags = MessageElementFlags();
|
||||||
auto emote = boost::optional<EmotePtr>{};
|
auto emote = boost::optional<EmotePtr>{};
|
||||||
|
|
||||||
if ((emote = this->twitchChannel->globalBttv().emote(name)))
|
// Emote order:
|
||||||
{
|
// - FrankerFaceZ Channel
|
||||||
flags = MessageElementFlag::BttvEmote;
|
// - BetterTTV Channel
|
||||||
}
|
// - FrankerFaceZ Global
|
||||||
else if ((emote = this->twitchChannel->bttvEmote(name)))
|
// - BetterTTV Global
|
||||||
{
|
if (this->twitchChannel && (emote = this->twitchChannel->ffzEmote(name)))
|
||||||
flags = MessageElementFlag::BttvEmote;
|
|
||||||
}
|
|
||||||
else if ((emote = this->twitchChannel->globalFfz().emote(name)))
|
|
||||||
{
|
{
|
||||||
flags = MessageElementFlag::FfzEmote;
|
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;
|
flags = MessageElementFlag::FfzEmote;
|
||||||
}
|
}
|
||||||
|
else if ((emote = globalBttvEmotes.emote(name)))
|
||||||
|
{
|
||||||
|
flags = MessageElementFlag::BttvEmote;
|
||||||
|
}
|
||||||
|
|
||||||
if (emote)
|
if (emote)
|
||||||
{
|
{
|
||||||
|
@ -1064,11 +1023,11 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (twitchChannel)
|
if (twitchChannel)
|
||||||
if (const auto &badge = this->twitchChannel->twitchBadge(
|
if (const auto &_badge = this->twitchChannel->twitchBadge(
|
||||||
"bits", cheerAmount))
|
"bits", cheerAmount))
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(
|
this->emplace<BadgeElement>(
|
||||||
badge.get(), MessageElementFlag::BadgeVanity)
|
_badge.get(), MessageElementFlag::BadgeVanity)
|
||||||
->setTooltip(tooltip);
|
->setTooltip(tooltip);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1079,10 +1038,10 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use default bit badge
|
// Use default bit badge
|
||||||
if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
|
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
|
||||||
"bits", cheerAmount))
|
"bits", cheerAmount))
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(badge.get(),
|
this->emplace<BadgeElement>(_badge.get(),
|
||||||
MessageElementFlag::BadgeVanity)
|
MessageElementFlag::BadgeVanity)
|
||||||
->setTooltip(tooltip);
|
->setTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
@ -1112,7 +1071,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
{
|
{
|
||||||
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
if (auto customModBadge = this->twitchChannel->ffzCustomModBadge())
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(
|
this->emplace<BadgeElement>(
|
||||||
customModBadge.get(),
|
customModBadge.get(),
|
||||||
MessageElementFlag::BadgeChannelAuthority)
|
MessageElementFlag::BadgeChannelAuthority)
|
||||||
->setTooltip((*customModBadge)->tooltip.string);
|
->setTooltip((*customModBadge)->tooltip.string);
|
||||||
|
@ -1172,7 +1131,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
if (auto badgeEmote = this->twitchChannel->twitchBadge(
|
if (auto badgeEmote = this->twitchChannel->twitchBadge(
|
||||||
"subscriber", badge.mid(11)))
|
"subscriber", badge.mid(11)))
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(
|
this->emplace<BadgeElement>(
|
||||||
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
|
badgeEmote.get(), MessageElementFlag::BadgeSubscription)
|
||||||
->setTooltip((*badgeEmote)->tooltip.string);
|
->setTooltip((*badgeEmote)->tooltip.string);
|
||||||
continue;
|
continue;
|
||||||
|
@ -1193,17 +1152,17 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
if (auto badgeEmote =
|
if (auto badgeEmote =
|
||||||
this->twitchChannel->twitchBadge(splits[0], splits[1]))
|
this->twitchChannel->twitchBadge(splits[0], splits[1]))
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(badgeEmote.get(),
|
this->emplace<BadgeElement>(badgeEmote.get(),
|
||||||
MessageElementFlag::BadgeVanity)
|
MessageElementFlag::BadgeVanity)
|
||||||
->setTooltip((*badgeEmote)->tooltip.string);
|
->setTooltip((*badgeEmote)->tooltip.string);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto badge = this->twitchChannel->globalTwitchBadges().badge(
|
if (auto _badge = this->twitchChannel->globalTwitchBadges().badge(
|
||||||
splits[0], splits[1]))
|
splits[0], splits[1]))
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(badge.get(),
|
this->emplace<BadgeElement>(_badge.get(),
|
||||||
MessageElementFlag::BadgeVanity)
|
MessageElementFlag::BadgeVanity)
|
||||||
->setTooltip((*badge)->tooltip.string);
|
->setTooltip((*_badge)->tooltip.string);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1216,7 +1175,7 @@ void TwitchMessageBuilder::appendChatterinoBadges()
|
||||||
getApp()->chatterinoBadges->getBadge({this->userName});
|
getApp()->chatterinoBadges->getBadge({this->userName});
|
||||||
if (chatterinoBadgePtr)
|
if (chatterinoBadgePtr)
|
||||||
{
|
{
|
||||||
this->emplace<EmoteElement>(*chatterinoBadgePtr,
|
this->emplace<BadgeElement>(*chatterinoBadgePtr,
|
||||||
MessageElementFlag::BadgeChatterino);
|
MessageElementFlag::BadgeChatterino);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ public:
|
||||||
MessageParseArgs args;
|
MessageParseArgs args;
|
||||||
const QVariantMap tags;
|
const QVariantMap tags;
|
||||||
|
|
||||||
QString messageID;
|
|
||||||
QString userName;
|
QString userName;
|
||||||
|
|
||||||
bool isIgnored() const;
|
bool isIgnored() const;
|
||||||
|
@ -55,8 +54,10 @@ private:
|
||||||
void appendUsername();
|
void appendUsername();
|
||||||
void parseHighlights(bool isPastMsg);
|
void parseHighlights(bool isPastMsg);
|
||||||
|
|
||||||
void appendTwitchEmote(const QString &emote,
|
void appendTwitchEmote(
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec, std::vector<int> &correctPositions);
|
const QString &emote,
|
||||||
|
std::vector<std::tuple<int, EmotePtr, EmoteName>> &vec,
|
||||||
|
std::vector<int> &correctPositions);
|
||||||
Outcome tryAppendEmote(const EmoteName &name);
|
Outcome tryAppendEmote(const EmoteName &name);
|
||||||
|
|
||||||
void addWords(
|
void addWords(
|
||||||
|
|
|
@ -156,6 +156,10 @@ void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
handler.handleClearChatMessage(message);
|
handler.handleClearChatMessage(message);
|
||||||
}
|
}
|
||||||
|
else if (command == "CLEARMSG")
|
||||||
|
{
|
||||||
|
handler.handleClearMessageMessage(message);
|
||||||
|
}
|
||||||
else if (command == "USERSTATE")
|
else if (command == "USERSTATE")
|
||||||
{
|
{
|
||||||
handler.handleUserStateMessage(message);
|
handler.handleUserStateMessage(message);
|
||||||
|
@ -219,7 +223,7 @@ std::shared_ptr<Channel> TwitchServer::getCustomChannel(
|
||||||
{
|
{
|
||||||
static auto channel =
|
static auto channel =
|
||||||
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
|
std::make_shared<Channel>("$$$", chatterino::Channel::Type::Misc);
|
||||||
static auto timer = [&] {
|
static auto getTimer = [&] {
|
||||||
for (auto i = 0; i < 1000; i++)
|
for (auto i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
|
channel->addMessage(makeSystemMessage(QString::number(i + 1)));
|
||||||
|
@ -264,7 +268,8 @@ std::shared_ptr<Channel> TwitchServer::getChannelOrEmptyByID(
|
||||||
if (!twitchChannel)
|
if (!twitchChannel)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (twitchChannel->roomId() == channelId)
|
if (twitchChannel->roomId() == channelId &&
|
||||||
|
twitchChannel->getName().splitRef(":").size() < 3)
|
||||||
{
|
{
|
||||||
return twitchChannel;
|
return twitchChannel;
|
||||||
}
|
}
|
||||||
|
@ -293,10 +298,11 @@ void TwitchServer::onMessageSendRequested(TwitchChannel *channel,
|
||||||
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
|
std::lock_guard<std::mutex> guard(this->lastMessageMutex_);
|
||||||
|
|
||||||
// std::queue<std::chrono::steady_clock::time_point>
|
// std::queue<std::chrono::steady_clock::time_point>
|
||||||
auto &lastMessage = channel->hasModRights() ? this->lastMessageMod_
|
auto &lastMessage = channel->hasHighRateLimit()
|
||||||
: this->lastMessagePleb_;
|
? this->lastMessageMod_
|
||||||
size_t maxMessageCount = channel->hasModRights() ? 99 : 19;
|
: this->lastMessagePleb_;
|
||||||
auto minMessageOffset = (channel->hasModRights() ? 100ms : 1100ms);
|
size_t maxMessageCount = channel->hasHighRateLimit() ? 99 : 19;
|
||||||
|
auto minMessageOffset = (channel->hasHighRateLimit() ? 100ms : 1100ms);
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ bool Paths::isPortable()
|
||||||
|
|
||||||
QString Paths::cacheDirectory()
|
QString Paths::cacheDirectory()
|
||||||
{
|
{
|
||||||
static QStringSetting cachePathSetting = [] {
|
static const auto pathSetting = [] {
|
||||||
QStringSetting cachePathSetting("/cache/path");
|
QStringSetting cachePathSetting("/cache/path");
|
||||||
|
|
||||||
cachePathSetting.connect([](const auto &newPath, auto) {
|
cachePathSetting.connect([](const auto &newPath, auto) {
|
||||||
|
@ -47,9 +47,9 @@ QString Paths::cacheDirectory()
|
||||||
return cachePathSetting;
|
return cachePathSetting;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
auto path = cachePathSetting.getValue();
|
auto path = pathSetting.getValue();
|
||||||
|
|
||||||
if (path == "")
|
if (path.isEmpty())
|
||||||
{
|
{
|
||||||
return this->cacheDirectory_;
|
return this->cacheDirectory_;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||||
#include "messages/HistoricMessageAppearance.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
|
|
||||||
#include <pajlada/settings/setting.hpp>
|
#include <pajlada/settings/setting.hpp>
|
||||||
#include <pajlada/settings/settinglistener.hpp>
|
#include <pajlada/settings/settinglistener.hpp>
|
||||||
|
@ -32,15 +32,15 @@ public:
|
||||||
Qt::VerPattern};
|
Qt::VerPattern};
|
||||||
QStringSetting lastMessageColor = {"/appearance/messages/lastMessageColor",
|
QStringSetting lastMessageColor = {"/appearance/messages/lastMessageColor",
|
||||||
""};
|
""};
|
||||||
IntSetting historicMessagesAppearance = {
|
|
||||||
"/appearance/messages/historicMessagesAppearance",
|
|
||||||
HistoricMessageAppearance::Crossed | HistoricMessageAppearance::Greyed};
|
|
||||||
BoolSetting showEmptyInput = {"/appearance/showEmptyInputBox", true};
|
BoolSetting showEmptyInput = {"/appearance/showEmptyInputBox", true};
|
||||||
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength",
|
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength",
|
||||||
false};
|
false};
|
||||||
BoolSetting separateMessages = {"/appearance/messages/separateMessages",
|
BoolSetting separateMessages = {"/appearance/messages/separateMessages",
|
||||||
false};
|
false};
|
||||||
BoolSetting compactEmotes = {"/appearance/messages/compactEmotes", true};
|
BoolSetting compactEmotes = {"/appearance/messages/compactEmotes", true};
|
||||||
|
BoolSetting hideModerated = {"/appearance/messages/hideModerated", false};
|
||||||
|
BoolSetting hideModerationActions = {
|
||||||
|
"/appearance/messages/hideModerationActions", false};
|
||||||
|
|
||||||
// BoolSetting collapseLongMessages =
|
// BoolSetting collapseLongMessages =
|
||||||
// {"/appearance/messages/collapseLongMessages", false};
|
// {"/appearance/messages/collapseLongMessages", false};
|
||||||
|
@ -48,7 +48,7 @@ public:
|
||||||
"/appearance/messages/collapseMessagesMinLines", 0};
|
"/appearance/messages/collapseMessagesMinLines", 0};
|
||||||
BoolSetting alternateMessages = {
|
BoolSetting alternateMessages = {
|
||||||
"/appearance/messages/alternateMessageBackground", false};
|
"/appearance/messages/alternateMessageBackground", false};
|
||||||
IntSetting boldScale = {"/appearance/boldScale", 57};
|
FloatSetting boldScale = {"/appearance/boldScale", 50};
|
||||||
BoolSetting showTabCloseButton = {"/appearance/showTabCloseButton", true};
|
BoolSetting showTabCloseButton = {"/appearance/showTabCloseButton", true};
|
||||||
BoolSetting showTabLive = {"/appearance/showTabLiveButton", false};
|
BoolSetting showTabLive = {"/appearance/showTabLiveButton", false};
|
||||||
BoolSetting hidePreferencesButton = {"/appearance/hidePreferencesButton",
|
BoolSetting hidePreferencesButton = {"/appearance/hidePreferencesButton",
|
||||||
|
@ -67,7 +67,6 @@ public:
|
||||||
BoolSetting headerUptime = {"/appearance/splitheader/showUptime", false};
|
BoolSetting headerUptime = {"/appearance/splitheader/showUptime", false};
|
||||||
FloatSetting customThemeMultiplier = {"/appearance/customThemeMultiplier",
|
FloatSetting customThemeMultiplier = {"/appearance/customThemeMultiplier",
|
||||||
-0.5f};
|
-0.5f};
|
||||||
BoolSetting redDisabledMessages = {"/appearance/redStripes", true};
|
|
||||||
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
|
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
|
||||||
// false};
|
// false};
|
||||||
|
|
||||||
|
@ -106,10 +105,7 @@ public:
|
||||||
/// Emotes
|
/// Emotes
|
||||||
BoolSetting scaleEmotesByLineHeight = {"/emotes/scaleEmotesByLineHeight",
|
BoolSetting scaleEmotesByLineHeight = {"/emotes/scaleEmotesByLineHeight",
|
||||||
false};
|
false};
|
||||||
BoolSetting enableTwitchEmotes = {"/emotes/enableTwitchEmotes", true};
|
BoolSetting enableEmoteImages = {"/emotes/enableEmoteImages", true};
|
||||||
BoolSetting enableBttvEmotes = {"/emotes/enableBTTVEmotes", true};
|
|
||||||
BoolSetting enableFfzEmotes = {"/emotes/enableFFZEmotes", true};
|
|
||||||
BoolSetting enableEmojis = {"/emotes/enableEmojis", true};
|
|
||||||
BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true};
|
BoolSetting animateEmotes = {"/emotes/enableGifAnimations", true};
|
||||||
FloatSetting emoteScale = {"/emotes/scale", 1.f};
|
FloatSetting emoteScale = {"/emotes/scale", 1.f};
|
||||||
|
|
||||||
|
@ -128,6 +124,7 @@ public:
|
||||||
/// Ingored Users
|
/// Ingored Users
|
||||||
BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers",
|
BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers",
|
||||||
true};
|
true};
|
||||||
|
IntSetting showIgnoredUsersMessages = {"/ignore/showIgnoredUsers", 0};
|
||||||
|
|
||||||
/// Moderation
|
/// Moderation
|
||||||
QStringSetting timeoutAction = {"/moderation/timeoutAction", "Disable"};
|
QStringSetting timeoutAction = {"/moderation/timeoutAction", "Disable"};
|
||||||
|
@ -147,7 +144,7 @@ public:
|
||||||
"/highlighting/whisperHighlight/enableSound", false};
|
"/highlighting/whisperHighlight/enableSound", false};
|
||||||
BoolSetting enableWhisperHighlightTaskbar = {
|
BoolSetting enableWhisperHighlightTaskbar = {
|
||||||
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
|
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
|
||||||
QStringSetting highlightColor = {"/highlighting/color", "#4B282C"};
|
QStringSetting highlightColor = {"/highlighting/color", ""};
|
||||||
|
|
||||||
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
||||||
|
|
||||||
|
@ -175,6 +172,8 @@ public:
|
||||||
"qrc:/sounds/ping3.wav"};
|
"qrc:/sounds/ping3.wav"};
|
||||||
|
|
||||||
BoolSetting notificationToast = {"/notifications/enableToast", false};
|
BoolSetting notificationToast = {"/notifications/enableToast", false};
|
||||||
|
IntSetting openFromToast = {"/notifications/openFromToast",
|
||||||
|
static_cast<int>(ToastReaction::OpenInBrowser)};
|
||||||
|
|
||||||
/// External tools
|
/// External tools
|
||||||
// Streamlink
|
// Streamlink
|
||||||
|
@ -188,6 +187,9 @@ public:
|
||||||
/// Misc
|
/// Misc
|
||||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||||
|
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||||
|
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
||||||
|
IntSetting emotesTooltipPreview = {"/misc/emotesTooltipPreview", 0};
|
||||||
|
|
||||||
QStringSetting cachePath = {"/cache/path", ""};
|
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);
|
return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto sat = qreal(0);
|
const auto sat = qreal(0);
|
||||||
auto isLight_ = this->isLightTheme();
|
const auto isLight = this->isLightTheme();
|
||||||
auto flat = isLight_;
|
const auto flat = isLight;
|
||||||
|
|
||||||
if (this->isLightTheme())
|
if (this->isLightTheme())
|
||||||
{
|
{
|
||||||
|
@ -38,6 +38,9 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
||||||
|
|
||||||
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
||||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
||||||
|
|
||||||
|
// Highlighted Messages: theme support quick-fix
|
||||||
|
this->messages.backgrounds.highlighted = QColor("#BD8489");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -46,13 +49,16 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
||||||
|
|
||||||
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
||||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
|
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.background = getColor(0, sat, flat ? 1 : 0.9);
|
||||||
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
|
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
|
||||||
this->splits.header.text = this->messages.textColors.regular;
|
this->splits.header.text = this->messages.textColors.regular;
|
||||||
this->splits.header.focusedText =
|
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.background = getColor(0, sat, flat ? 0.95 : 0.95);
|
||||||
this->splits.input.border = getColor(0, sat, flat ? 1 : 1);
|
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() +
|
"border:" + this->tabs.selected.backgrounds.regular.color().name() +
|
||||||
";" + "color:" + this->messages.textColors.regular.name() + ";" + //
|
";" + "color:" + this->messages.textColors.regular.name() + ";" + //
|
||||||
"selection-background-color:" +
|
"selection-background-color:" +
|
||||||
(isLight_ ? "#68B1FF"
|
(isLight ? "#68B1FF"
|
||||||
: this->tabs.selected.backgrounds.regular.color().name());
|
: this->tabs.selected.backgrounds.regular.color().name());
|
||||||
|
|
||||||
this->splits.input.focusedLine = this->tabs.highlighted.line.regular;
|
this->splits.input.focusedLine = this->tabs.highlighted.line.regular;
|
||||||
|
|
||||||
this->splits.messageSeperator =
|
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.background = getColor(0, sat, 1);
|
||||||
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
|
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
|
||||||
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
|
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
|
||||||
|
|
||||||
// Highlighted Messages
|
// Highlighted Messages
|
||||||
this->messages.backgrounds.highlighted =
|
// hidden setting from PR #744 - if set it will overwrite theme color
|
||||||
QColor(getSettings()->highlightColor);
|
// TODO: implement full theme support
|
||||||
|
if (getSettings()->highlightColor != "")
|
||||||
|
{
|
||||||
|
this->messages.backgrounds.highlighted =
|
||||||
|
QColor(getSettings()->highlightColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Theme::normalizeColor(QColor &color)
|
void Theme::normalizeColor(QColor &color)
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchServer.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
|
#include "util/StreamLink.hpp"
|
||||||
|
#include "widgets/helper/CommonTexts.hpp"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|
||||||
|
@ -25,13 +27,40 @@
|
||||||
|
|
||||||
namespace chatterino {
|
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()
|
bool Toasts::isEnabled()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
return WinToastLib::WinToast::isCompatible() &&
|
return WinToastLib::WinToast::isCompatible() &&
|
||||||
getSettings()->notificationToast;
|
getSettings()->notificationToast;
|
||||||
#endif
|
#else
|
||||||
return false;
|
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)
|
void Toasts::sendChannelNotification(const QString &channelName, Platform p)
|
||||||
|
@ -86,11 +115,33 @@ public:
|
||||||
void toastActivated() const
|
void toastActivated() const
|
||||||
{
|
{
|
||||||
QString link;
|
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
|
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());
|
std::wstring widestr = std::wstring(utf8_text.begin(), utf8_text.end());
|
||||||
|
|
||||||
templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
|
templ.setTextField(widestr, WinToastLib::WinToastTemplate::FirstLine);
|
||||||
templ.setTextField(L"Click here to open in browser",
|
if (static_cast<ToastReaction>(getSettings()->openFromToast.getValue()) !=
|
||||||
WinToastLib::WinToastTemplate::SecondLine);
|
ToastReaction::DontOpen)
|
||||||
|
{
|
||||||
|
QString mode =
|
||||||
|
Toasts::findStringFromReaction(getSettings()->openFromToast);
|
||||||
|
mode = mode.toLower();
|
||||||
|
|
||||||
|
templ.setTextField(L"Click here to " + mode.toStdWString(),
|
||||||
|
WinToastLib::WinToastTemplate::SecondLine);
|
||||||
|
}
|
||||||
|
|
||||||
QString Path;
|
QString Path;
|
||||||
if (p == Platform::Twitch)
|
if (p == Platform::Twitch)
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,10 +7,21 @@ namespace chatterino {
|
||||||
|
|
||||||
enum class Platform : uint8_t;
|
enum class Platform : uint8_t;
|
||||||
|
|
||||||
|
enum class ToastReaction {
|
||||||
|
OpenInBrowser = 0,
|
||||||
|
OpenInPlayer = 1,
|
||||||
|
OpenInStreamlink = 2,
|
||||||
|
DontOpen = 3
|
||||||
|
};
|
||||||
|
|
||||||
class Toasts final : public Singleton
|
class Toasts final : public Singleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void sendChannelNotification(const QString &channelName, Platform p);
|
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();
|
static bool isEnabled();
|
||||||
|
|
||||||
|
@ -18,6 +29,7 @@ private:
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
void sendWindowsNotification(const QString &channelName, Platform p);
|
void sendWindowsNotification(const QString &channelName, Platform p);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void fetchChannelAvatar(
|
static void fetchChannelAvatar(
|
||||||
const QString channelName,
|
const QString channelName,
|
||||||
std::function<void(QString)> successCallback);
|
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 <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QSaveFile>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
@ -75,10 +76,7 @@ WindowManager::WindowManager()
|
||||||
this->wordFlagsListener_.addSetting(settings->showBadgesSubscription);
|
this->wordFlagsListener_.addSetting(settings->showBadgesSubscription);
|
||||||
this->wordFlagsListener_.addSetting(settings->showBadgesVanity);
|
this->wordFlagsListener_.addSetting(settings->showBadgesVanity);
|
||||||
this->wordFlagsListener_.addSetting(settings->showBadgesChatterino);
|
this->wordFlagsListener_.addSetting(settings->showBadgesChatterino);
|
||||||
this->wordFlagsListener_.addSetting(settings->enableBttvEmotes);
|
this->wordFlagsListener_.addSetting(settings->enableEmoteImages);
|
||||||
this->wordFlagsListener_.addSetting(settings->enableEmojis);
|
|
||||||
this->wordFlagsListener_.addSetting(settings->enableFfzEmotes);
|
|
||||||
this->wordFlagsListener_.addSetting(settings->enableTwitchEmotes);
|
|
||||||
this->wordFlagsListener_.addSetting(settings->boldUsernames);
|
this->wordFlagsListener_.addSetting(settings->boldUsernames);
|
||||||
this->wordFlagsListener_.addSetting(settings->lowercaseDomains);
|
this->wordFlagsListener_.addSetting(settings->lowercaseDomains);
|
||||||
this->wordFlagsListener_.setCB([this] {
|
this->wordFlagsListener_.setCB([this] {
|
||||||
|
@ -114,13 +112,12 @@ void WindowManager::updateWordTypeMask()
|
||||||
}
|
}
|
||||||
|
|
||||||
// emotes
|
// emotes
|
||||||
flags.set(settings->enableTwitchEmotes ? MEF::TwitchEmoteImage
|
if (settings->enableEmoteImages)
|
||||||
: MEF::TwitchEmoteText);
|
{
|
||||||
flags.set(settings->enableFfzEmotes ? MEF::FfzEmoteImage
|
flags.set(MEF::EmoteImages);
|
||||||
: MEF::FfzEmoteText);
|
}
|
||||||
flags.set(settings->enableBttvEmotes ? MEF::BttvEmoteImage
|
flags.set(MEF::EmoteText);
|
||||||
: MEF::BttvEmoteText);
|
flags.set(MEF::EmojiText);
|
||||||
flags.set(settings->enableEmojis ? MEF::EmojiImage : MEF::EmojiText);
|
|
||||||
|
|
||||||
// bits
|
// bits
|
||||||
flags.set(MEF::BitsAmount);
|
flags.set(MEF::BitsAmount);
|
||||||
|
@ -258,11 +255,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
|
||||||
|
|
||||||
// load file
|
// load file
|
||||||
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
|
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
|
||||||
QFile file(settingsPath);
|
QJsonArray windows_arr = this->loadWindowArray(settingsPath);
|
||||||
file.open(QIODevice::ReadOnly);
|
|
||||||
QByteArray data = file.readAll();
|
|
||||||
QJsonDocument document = QJsonDocument::fromJson(data);
|
|
||||||
QJsonArray windows_arr = document.object().value("windows").toArray();
|
|
||||||
|
|
||||||
// "deserialize"
|
// "deserialize"
|
||||||
for (QJsonValue window_val : windows_arr)
|
for (QJsonValue window_val : windows_arr)
|
||||||
|
@ -390,10 +383,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
|
||||||
void WindowManager::save()
|
void WindowManager::save()
|
||||||
{
|
{
|
||||||
log("[WindowManager] Saving");
|
log("[WindowManager] Saving");
|
||||||
|
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
QJsonDocument document;
|
QJsonDocument document;
|
||||||
|
|
||||||
// "serialize"
|
// "serialize"
|
||||||
|
@ -477,7 +467,7 @@ void WindowManager::save()
|
||||||
|
|
||||||
// save file
|
// save file
|
||||||
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
|
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
|
||||||
QFile file(settingsPath);
|
QSaveFile file(settingsPath);
|
||||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||||
|
|
||||||
QJsonDocument::JsonFormat format =
|
QJsonDocument::JsonFormat format =
|
||||||
|
@ -489,7 +479,7 @@ void WindowManager::save()
|
||||||
;
|
;
|
||||||
|
|
||||||
file.write(document.toJson(format));
|
file.write(document.toJson(format));
|
||||||
file.flush();
|
file.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowManager::sendAlert()
|
void WindowManager::sendAlert()
|
||||||
|
@ -516,6 +506,7 @@ void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
|
||||||
case SplitNode::_Split:
|
case SplitNode::_Split:
|
||||||
{
|
{
|
||||||
obj.insert("type", "split");
|
obj.insert("type", "split");
|
||||||
|
obj.insert("moderationMode", node->getSplit()->getModerationMode());
|
||||||
QJsonObject split;
|
QJsonObject split;
|
||||||
encodeChannel(node->getSplit()->getIndirectChannel(), split);
|
encodeChannel(node->getSplit()->getIndirectChannel(), split);
|
||||||
obj.insert("data", split);
|
obj.insert("data", split);
|
||||||
|
@ -621,4 +612,14 @@ void WindowManager::incGeneration()
|
||||||
this->generation_++;
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -54,6 +54,7 @@ public:
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
virtual void initialize(Settings &settings, Paths &paths) override;
|
||||||
virtual void save() override;
|
virtual void save() override;
|
||||||
void closeAll();
|
void closeAll();
|
||||||
|
QJsonArray loadWindowArray(const QString &settingsPath);
|
||||||
|
|
||||||
int getGeneration() const;
|
int getGeneration() const;
|
||||||
void incGeneration();
|
void incGeneration();
|
||||||
|
|
|
@ -33,20 +33,9 @@ LoggingChannel::LoggingChannel(const QString &_channelName)
|
||||||
// FOURTF: change this when adding more providers
|
// FOURTF: change this when adding more providers
|
||||||
this->subDirectory = "Twitch/" + this->subDirectory;
|
this->subDirectory = "Twitch/" + this->subDirectory;
|
||||||
|
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
getSettings()->logPath.connect([this](const QString &logPath, auto) {
|
getSettings()->logPath.connect([this](const QString &logPath, auto) {
|
||||||
auto app = getApp();
|
this->baseDirectory =
|
||||||
|
logPath.isEmpty() ? getPaths()->messageLogDirectory : logPath;
|
||||||
if (logPath.isEmpty())
|
|
||||||
{
|
|
||||||
this->baseDirectory = getPaths()->messageLogDirectory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->baseDirectory = logPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->openLogFile();
|
this->openLogFile();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,6 @@ namespace {
|
||||||
|
|
||||||
QString getStreamlinkProgram()
|
QString getStreamlinkProgram()
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
if (getSettings()->streamlinkUseCustomPath)
|
if (getSettings()->streamlinkUseCustomPath)
|
||||||
{
|
{
|
||||||
return getSettings()->streamlinkPath + "/" + getBinaryName();
|
return getSettings()->streamlinkPath + "/" + getBinaryName();
|
||||||
|
@ -66,7 +64,6 @@ namespace {
|
||||||
{
|
{
|
||||||
static QErrorMessage *msg = new QErrorMessage;
|
static QErrorMessage *msg = new QErrorMessage;
|
||||||
|
|
||||||
auto app = getApp();
|
|
||||||
if (getSettings()->streamlinkUseCustomPath)
|
if (getSettings()->streamlinkUseCustomPath)
|
||||||
{
|
{
|
||||||
msg->showMessage(
|
msg->showMessage(
|
||||||
|
@ -172,8 +169,6 @@ void getStreamQualities(const QString &channelURL,
|
||||||
void openStreamlink(const QString &channelURL, const QString &quality,
|
void openStreamlink(const QString &channelURL, const QString &quality,
|
||||||
QStringList extraArguments)
|
QStringList extraArguments)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
QStringList arguments;
|
QStringList arguments;
|
||||||
|
|
||||||
QString additionalOptions = getSettings()->streamlinkOpts.getValue();
|
QString additionalOptions = getSettings()->streamlinkOpts.getValue();
|
||||||
|
@ -202,8 +197,6 @@ void openStreamlink(const QString &channelURL, const QString &quality,
|
||||||
|
|
||||||
void openStreamlinkForChannel(const QString &channel)
|
void openStreamlinkForChannel(const QString &channel)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
QString channelURL = "twitch.tv/" + channel;
|
QString channelURL = "twitch.tv/" + channel;
|
||||||
|
|
||||||
QString preferredQuality = getSettings()->preferredQuality;
|
QString preferredQuality = getSettings()->preferredQuality;
|
||||||
|
|
|
@ -14,6 +14,9 @@ AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
this->setWindowFlag(Qt::Popup);
|
||||||
|
#endif
|
||||||
|
|
||||||
this->setContentsMargins(0, 0, 0, 0);
|
this->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/InitUpdateButton.hpp"
|
#include "util/InitUpdateButton.hpp"
|
||||||
|
#include "util/Shortcut.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||||
#include "widgets/helper/NotebookButton.hpp"
|
#include "widgets/helper/NotebookButton.hpp"
|
||||||
#include "widgets/helper/NotebookTab.hpp"
|
#include "widgets/helper/NotebookTab.hpp"
|
||||||
#include "util/Shortcut.hpp"
|
|
||||||
#include "widgets/splits/Split.hpp"
|
#include "widgets/splits/Split.hpp"
|
||||||
#include "widgets/splits/SplitContainer.hpp"
|
#include "widgets/splits/SplitContainer.hpp"
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ void Scrollbar::setSmallChange(qreal value)
|
||||||
|
|
||||||
void Scrollbar::setDesiredValue(qreal value, bool animated)
|
void Scrollbar::setDesiredValue(qreal value, bool animated)
|
||||||
{
|
{
|
||||||
animated &= getSettings()->enableSmoothScrolling.getValue();
|
animated &= getSettings()->enableSmoothScrolling;
|
||||||
value = std::max(this->minimum_,
|
value = std::max(this->minimum_,
|
||||||
std::min(this->maximum_ - this->largeChange_, value));
|
std::min(this->maximum_ - this->largeChange_, value));
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
#include <QMenuBar>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -43,6 +44,10 @@ Window::Window(WindowType type)
|
||||||
this->addShortcuts();
|
this->addShortcuts();
|
||||||
this->addLayout();
|
this->addLayout();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
this->addMenuBar();
|
||||||
|
#endif
|
||||||
|
|
||||||
this->signalHolder_.managedConnect(
|
this->signalHolder_.managedConnect(
|
||||||
getApp()->accounts->twitch.currentUserChanged,
|
getApp()->accounts->twitch.currentUserChanged,
|
||||||
[this] { this->onAccountSelected(); });
|
[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 UGLYMACROHACK1(s) #s
|
||||||
#define UGLYMACROHACK(s) UGLYMACROHACK1(s)
|
#define UGLYMACROHACK(s) UGLYMACROHACK1(s)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ private:
|
||||||
void addShortcuts();
|
void addShortcuts();
|
||||||
void addLayout();
|
void addLayout();
|
||||||
void onAccountSelected();
|
void onAccountSelected();
|
||||||
|
void addMenuBar();
|
||||||
|
|
||||||
WindowType type_;
|
WindowType type_;
|
||||||
|
|
||||||
|
|
|
@ -319,7 +319,6 @@ bool SelectChannelDialog::EventFilter::eventFilter(QObject *watched,
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else if (event->type() == QEvent::KeyRelease)
|
else if (event->type() == QEvent::KeyRelease)
|
||||||
{
|
{
|
||||||
|
|
|
@ -84,6 +84,8 @@ UserInfoPopup::UserInfoPopup()
|
||||||
.assign(&this->ui_.ignoreHighlights);
|
.assign(&this->ui_.ignoreHighlights);
|
||||||
auto viewLogs = user.emplace<EffectLabel2>(this);
|
auto viewLogs = user.emplace<EffectLabel2>(this);
|
||||||
viewLogs->getLabel().setText("Online logs");
|
viewLogs->getLabel().setText("Online logs");
|
||||||
|
auto usercard = user.emplace<EffectLabel2>(this);
|
||||||
|
usercard->getLabel().setText("Usercard");
|
||||||
|
|
||||||
auto mod = user.emplace<Button>(this);
|
auto mod = user.emplace<Button>(this);
|
||||||
mod->setPixmap(app->resources->buttons.mod);
|
mod->setPixmap(app->resources->buttons.mod);
|
||||||
|
@ -103,6 +105,12 @@ UserInfoPopup::UserInfoPopup()
|
||||||
logs->show();
|
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] {
|
QObject::connect(mod.getElement(), &Button::leftClicked, [this] {
|
||||||
this->channel_->sendMessage("/mod " + this->userName_);
|
this->channel_->sendMessage("/mod " + this->userName_);
|
||||||
});
|
});
|
||||||
|
@ -128,8 +136,8 @@ UserInfoPopup::UserInfoPopup()
|
||||||
this->userName_, Qt::CaseInsensitive) == 0;
|
this->userName_, Qt::CaseInsensitive) == 0;
|
||||||
|
|
||||||
visibilityMod = twitchChannel->isBroadcaster() && !isMyself;
|
visibilityMod = twitchChannel->isBroadcaster() && !isMyself;
|
||||||
visibilityUnmod = visibilityMod ||
|
visibilityUnmod =
|
||||||
(twitchChannel->isMod() && isMyself);
|
visibilityMod || (twitchChannel->isMod() && isMyself);
|
||||||
}
|
}
|
||||||
mod->setVisible(visibilityMod);
|
mod->setVisible(visibilityMod);
|
||||||
unmod->setVisible(visibilityUnmod);
|
unmod->setVisible(visibilityUnmod);
|
||||||
|
@ -147,8 +155,8 @@ UserInfoPopup::UserInfoPopup()
|
||||||
TwitchChannel *twitchChannel =
|
TwitchChannel *twitchChannel =
|
||||||
dynamic_cast<TwitchChannel *>(this->channel_.get());
|
dynamic_cast<TwitchChannel *>(this->channel_.get());
|
||||||
|
|
||||||
bool hasModRights = twitchChannel ? twitchChannel->hasModRights()
|
bool hasModRights =
|
||||||
: false;
|
twitchChannel ? twitchChannel->hasModRights() : false;
|
||||||
lineMod->setVisible(hasModRights);
|
lineMod->setVisible(hasModRights);
|
||||||
timeout->setVisible(hasModRights);
|
timeout->setVisible(hasModRights);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "providers/twitch/TwitchServer.hpp"
|
#include "providers/twitch/TwitchServer.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
|
#include "singletons/TooltipPreviewImage.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/DistanceBetweenPoints.hpp"
|
#include "util/DistanceBetweenPoints.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
|
@ -294,8 +295,12 @@ void ChannelView::scaleChangedEvent(float scale)
|
||||||
|
|
||||||
if (this->goToBottom_)
|
if (this->goToBottom_)
|
||||||
{
|
{
|
||||||
|
auto factor = this->qtFontScale();
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
factor = scale * 80.f / this->logicalDpiX() * this->devicePixelRatioF();
|
||||||
|
#endif
|
||||||
this->goToBottom_->getLabel().setFont(
|
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->repaint();
|
||||||
|
|
||||||
this->update();
|
this->update();
|
||||||
|
|
||||||
// this->updateTimer.start();
|
// this->updateTimer.start();
|
||||||
|
@ -1213,6 +1219,28 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
||||||
}
|
}
|
||||||
else
|
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->moveTo(this, event->globalPos());
|
||||||
tooltipWidget->setWordWrap(isLinkValid);
|
tooltipWidget->setWordWrap(isLinkValid);
|
||||||
tooltipWidget->setText(tooltip);
|
tooltipWidget->setText(tooltip);
|
||||||
|
@ -1667,7 +1695,12 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
||||||
case Link::UserAction:
|
case Link::UserAction:
|
||||||
{
|
{
|
||||||
QString value = link.value;
|
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);
|
this->channel_->sendMessage(value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "messages/LimitedQueue.hpp"
|
#include "messages/LimitedQueue.hpp"
|
||||||
#include "messages/LimitedQueueSnapshot.hpp"
|
#include "messages/LimitedQueueSnapshot.hpp"
|
||||||
#include "messages/Selection.hpp"
|
#include "messages/Selection.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "widgets/BaseWidget.hpp"
|
#include "widgets/BaseWidget.hpp"
|
||||||
|
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
|
@ -25,7 +26,7 @@ using ChannelPtr = std::shared_ptr<Channel>;
|
||||||
struct Message;
|
struct Message;
|
||||||
using MessagePtr = std::shared_ptr<const Message>;
|
using MessagePtr = std::shared_ptr<const Message>;
|
||||||
|
|
||||||
enum class MessageFlag : uint16_t;
|
enum class MessageFlag : uint32_t;
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
class MessageLayout;
|
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)
|
, notebook_(notebook)
|
||||||
, menu_(this)
|
, menu_(this)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
this->setAcceptDrops(true);
|
this->setAcceptDrops(true);
|
||||||
|
|
||||||
this->positionChangedAnimation_.setEasingCurve(
|
this->positionChangedAnimation_.setEasingCurve(
|
||||||
|
@ -527,8 +525,6 @@ void NotebookTab::dragEnterEvent(QDragEnterEvent *event)
|
||||||
|
|
||||||
void NotebookTab::mouseMoveEvent(QMouseEvent *event)
|
void NotebookTab::mouseMoveEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
if (getSettings()->showTabCloseButton &&
|
if (getSettings()->showTabCloseButton &&
|
||||||
this->notebook_->getAllowUserTabManagement()) //
|
this->notebook_->getAllowUserTabManagement()) //
|
||||||
{
|
{
|
||||||
|
|
|
@ -98,7 +98,7 @@ void ResizingTextEdit::keyPressEvent(QKeyEvent *event)
|
||||||
QString currentCompletionPrefix = this->textUnderCursor();
|
QString currentCompletionPrefix = this->textUnderCursor();
|
||||||
|
|
||||||
// check if there is something to complete
|
// check if there is something to complete
|
||||||
if (!currentCompletionPrefix.size())
|
if (currentCompletionPrefix.size() <= 1)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,27 @@ void ResizingTextEdit::insertCompletion(const QString &completion)
|
||||||
this->setTextCursor(tc);
|
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
|
QCompleter *ResizingTextEdit::getCompleter() const
|
||||||
{
|
{
|
||||||
return this->completer_;
|
return this->completer_;
|
||||||
|
|
|
@ -30,6 +30,9 @@ protected:
|
||||||
void focusInEvent(QFocusEvent *event) override;
|
void focusInEvent(QFocusEvent *event) override;
|
||||||
void focusOutEvent(QFocusEvent *event) override;
|
void focusOutEvent(QFocusEvent *event) override;
|
||||||
|
|
||||||
|
bool canInsertFromMimeData(const QMimeData *source) const override;
|
||||||
|
void insertFromMimeData(const QMimeData *source) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// hadSpace is set to true in case the "textUnderCursor" word was after a
|
// hadSpace is set to true in case the "textUnderCursor" word was after a
|
||||||
// space
|
// space
|
||||||
|
|
|
@ -17,6 +17,25 @@ SearchPopup::SearchPopup()
|
||||||
this->resize(400, 600);
|
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()
|
void SearchPopup::initLayout()
|
||||||
{
|
{
|
||||||
// VBOX
|
// 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()
|
void SearchPopup::performSearch()
|
||||||
{
|
{
|
||||||
QString text = searchInput_->text();
|
QString text = searchInput_->text();
|
||||||
|
|
|
@ -22,6 +22,9 @@ public:
|
||||||
|
|
||||||
void setChannel(std::shared_ptr<Channel> channel);
|
void setChannel(std::shared_ptr<Channel> channel);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initLayout();
|
void initLayout();
|
||||||
void performSearch();
|
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>("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>"
|
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);
|
"(<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
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ namespace chatterino {
|
||||||
AdvancedPage::AdvancedPage()
|
AdvancedPage::AdvancedPage()
|
||||||
: SettingsPage("Advanced", ":/settings/advanced.svg")
|
: SettingsPage("Advanced", ":/settings/advanced.svg")
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
LayoutCreator<AdvancedPage> layoutCreator(this);
|
LayoutCreator<AdvancedPage> layoutCreator(this);
|
||||||
|
|
||||||
auto tabs = layoutCreator.emplace<QTabWidget>();
|
auto tabs = layoutCreator.emplace<QTabWidget>();
|
||||||
|
|
|
@ -14,8 +14,6 @@ namespace chatterino {
|
||||||
ExternalToolsPage::ExternalToolsPage()
|
ExternalToolsPage::ExternalToolsPage()
|
||||||
: SettingsPage("External tools", ":/settings/externaltools.svg")
|
: SettingsPage("External tools", ":/settings/externaltools.svg")
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
|
||||||
|
|
||||||
LayoutCreator<ExternalToolsPage> layoutCreator(this);
|
LayoutCreator<ExternalToolsPage> layoutCreator(this);
|
||||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
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 tab close button", s.showTabCloseButton);
|
||||||
layout.addCheckbox("Show input when empty", s.showEmptyInput);
|
layout.addCheckbox("Show input when empty", s.showEmptyInput);
|
||||||
layout.addCheckbox("Show input message length", s.showMessageLength);
|
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.addTitle("Messages");
|
||||||
layout.addCheckbox("Timestamps", s.showTimestamps);
|
layout.addCheckbox("Timestamps", s.showTimestamps);
|
||||||
|
@ -197,8 +200,8 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
s.timestampFormat, true);
|
s.timestampFormat, true);
|
||||||
layout.addDropdown<int>(
|
layout.addDropdown<int>(
|
||||||
"Collapse messages",
|
"Collapse messages",
|
||||||
{"Never", "Longer than 2 lines", "Longer than 3 lines",
|
{"Never", "After 2 lines", "After 3 lines", "After 4 lines",
|
||||||
"Longer than 4 lines", "Longer than 5 lines"},
|
"After 5 lines"},
|
||||||
s.collpseMessagesMinLines,
|
s.collpseMessagesMinLines,
|
||||||
[](auto val) {
|
[](auto val) {
|
||||||
return val ? QString("After ") + QString::number(val) + " lines"
|
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("Alternate background color", s.alternateMessages);
|
||||||
// layout.addCheckbox("Mark last message you read");
|
// layout.addCheckbox("Mark last message you read");
|
||||||
// layout.addDropdown("Last read message style", {"Default"});
|
// 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.addTitle("Emotes");
|
||||||
layout.addDropdown<float>(
|
layout.addDropdown<float>(
|
||||||
|
@ -223,6 +228,7 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
[](auto args) { return fuzzyToFloat(args.value, 1.f); });
|
[](auto args) { return fuzzyToFloat(args.value, 1.f); });
|
||||||
layout.addCheckbox("Gif animations", s.animateEmotes);
|
layout.addCheckbox("Gif animations", s.animateEmotes);
|
||||||
layout.addCheckbox("Animate only when focused", s.animationsWhenFocused);
|
layout.addCheckbox("Animate only when focused", s.animationsWhenFocused);
|
||||||
|
layout.addCheckbox("Emote images", s.enableEmoteImages);
|
||||||
layout.addDropdown("Emoji set",
|
layout.addDropdown("Emoji set",
|
||||||
{"EmojiOne 2", "EmojiOne 3", "Twitter", "Facebook",
|
{"EmojiOne 2", "EmojiOne 3", "Twitter", "Facebook",
|
||||||
"Apple", "Google", "Messenger"},
|
"Apple", "Google", "Messenger"},
|
||||||
|
@ -249,63 +255,35 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
layout.addTitle("Miscellaneous");
|
layout.addTitle("Miscellaneous");
|
||||||
layout.addCheckbox("Show joined users (< 1000 chatters)", s.showJoins);
|
layout.addCheckbox("Show joined users (< 1000 chatters)", s.showJoins);
|
||||||
layout.addCheckbox("Show parted users (< 1000 chatters)", s.showParts);
|
layout.addCheckbox("Show parted users (< 1000 chatters)", s.showParts);
|
||||||
layout.addDropdown("Boldness", {"Not implemented"});
|
|
||||||
layout.addCheckbox("Lowercase domains", s.lowercaseDomains);
|
layout.addCheckbox("Lowercase domains", s.lowercaseDomains);
|
||||||
layout.addCheckbox("Bold @usernames", s.boldUsernames);
|
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("Show link info when hovering", s.linkInfoTooltip);
|
||||||
layout.addCheckbox("Double click links to open", s.linksDoubleClickOnly);
|
layout.addCheckbox("Double click links to open", s.linksDoubleClickOnly);
|
||||||
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
layout.addCheckbox("Unshorten links", s.unshortLinks);
|
||||||
layout.addCheckbox("Show live indicator in tabs", s.showTabLive);
|
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.addSpacing(16);
|
||||||
layout.addSeperator();
|
layout.addSeperator();
|
||||||
|
|
||||||
layout.addTitle2("Misc");
|
layout.addTitle2("Miscellaneous (Twitch)");
|
||||||
layout.addCheckbox("Show twitch whispers inline", s.inlineWhispers);
|
layout.addCheckbox("Show twitch whispers inline", s.inlineWhispers);
|
||||||
layout.addDropdown<int>(
|
layout.addCheckbox("Load message history on connect",
|
||||||
"Historic messages appearance",
|
s.loadTwitchMessageHistoryOnConnect);
|
||||||
{"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.addTitle2("Cache");
|
layout.addTitle2("Cache");
|
||||||
|
|
|
@ -77,8 +77,24 @@ void addUsersTab(IgnoresPage &page, LayoutCreator<QVBoxLayout> users,
|
||||||
|
|
||||||
auto anyways = users.emplace<QHBoxLayout>().withoutMargin();
|
auto anyways = users.emplace<QHBoxLayout>().withoutMargin();
|
||||||
{
|
{
|
||||||
anyways.emplace<QLabel>("Show anyways if:");
|
anyways.emplace<QLabel>("Show messages from ignored users anyways:");
|
||||||
anyways.emplace<QComboBox>();
|
|
||||||
|
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);
|
anyways->addStretch(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ KeyboardSettingsPage::KeyboardSettingsPage()
|
||||||
new QLabel("Search in current channel"));
|
new QLabel("Search in current channel"));
|
||||||
form->addRow(new QLabel("Ctrl + E"), new QLabel("Open Emote menu"));
|
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("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
|
} // namespace chatterino
|
||||||
|
|
|
@ -182,12 +182,6 @@ void LookPage::addMessageTab(LayoutCreator<QVBoxLayout> layout)
|
||||||
|
|
||||||
layout.append(
|
layout.append(
|
||||||
this->createCheckBox("Compact emotes", getSettings()->compactEmotes));
|
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);
|
layout.emplace<Line>(false);
|
||||||
|
|
||||||
// bold-slider
|
// bold-slider
|
||||||
|
|
|
@ -61,15 +61,10 @@ QString formatSize(qint64 size)
|
||||||
|
|
||||||
QString fetchLogDirectorySize()
|
QString fetchLogDirectorySize()
|
||||||
{
|
{
|
||||||
QString logPathDirectory;
|
QString logPathDirectory = getSettings()->logPath.getValue().isEmpty()
|
||||||
if (getSettings()->logPath == "")
|
? getPaths()->messageLogDirectory
|
||||||
{
|
: getSettings()->logPath;
|
||||||
logPathDirectory = getPaths()->messageLogDirectory;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logPathDirectory = getSettings()->logPath;
|
|
||||||
}
|
|
||||||
qint64 logsSize = dirSize(logPathDirectory);
|
qint64 logsSize = dirSize(logPathDirectory);
|
||||||
QString logsSizeLabel = "Your logs currently take up ";
|
QString logsSizeLabel = "Your logs currently take up ";
|
||||||
logsSizeLabel += formatSize(logsSize);
|
logsSizeLabel += formatSize(logsSize);
|
||||||
|
@ -96,31 +91,22 @@ ModerationPage::ModerationPage()
|
||||||
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
QtConcurrent::run([] { return fetchLogDirectorySize(); }));
|
||||||
|
|
||||||
// Logs (copied from LoggingMananger)
|
// Logs (copied from LoggingMananger)
|
||||||
getSettings()->logPath.connect(
|
getSettings()->logPath.connect([logsPathLabel](const QString &logPath,
|
||||||
[logsPathLabel](const QString &logPath, auto) mutable {
|
auto) mutable {
|
||||||
QString pathOriginal;
|
QString pathOriginal =
|
||||||
|
logPath.isEmpty() ? getPaths()->messageLogDirectory : logPath;
|
||||||
|
|
||||||
if (logPath == "")
|
QString pathShortened =
|
||||||
{
|
"Logs are saved at <a href=\"file:///" + pathOriginal +
|
||||||
pathOriginal = getPaths()->messageLogDirectory;
|
"\"><span style=\"color: white;\">" +
|
||||||
}
|
shortenString(pathOriginal, 50) + "</span></a>";
|
||||||
else
|
|
||||||
{
|
|
||||||
pathOriginal = logPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString pathShortened =
|
logsPathLabel->setText(pathShortened);
|
||||||
"Logs are saved at <a href=\"file:///" + pathOriginal +
|
logsPathLabel->setToolTip(pathOriginal);
|
||||||
"\"><span style=\"color: white;\">" +
|
});
|
||||||
shortenString(pathOriginal, 50) + "</span></a>";
|
|
||||||
|
|
||||||
logsPathLabel->setText(pathShortened);
|
|
||||||
logsPathLabel->setToolTip(pathOriginal);
|
|
||||||
});
|
|
||||||
|
|
||||||
logsPathLabel->setTextFormat(Qt::RichText);
|
logsPathLabel->setTextFormat(Qt::RichText);
|
||||||
logsPathLabel->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
logsPathLabel->setTextInteractionFlags(Qt::TextBrowserInteraction |
|
||||||
Qt::LinksAccessibleByKeyboard |
|
|
||||||
Qt::LinksAccessibleByKeyboard);
|
Qt::LinksAccessibleByKeyboard);
|
||||||
logsPathLabel->setOpenExternalLinks(true);
|
logsPathLabel->setOpenExternalLinks(true);
|
||||||
logs.append(this->createCheckBox("Enable logging",
|
logs.append(this->createCheckBox("Enable logging",
|
||||||
|
@ -161,7 +147,8 @@ ModerationPage::ModerationPage()
|
||||||
// clang-format off
|
// clang-format off
|
||||||
auto label = modMode.emplace<QLabel>(
|
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 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->setWordWrap(true);
|
||||||
label->setStyleSheet("color: #bbb");
|
label->setStyleSheet("color: #bbb");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "controllers/notifications/NotificationModel.hpp"
|
#include "controllers/notifications/NotificationModel.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/Toasts.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "widgets/helper/EditableModelView.hpp"
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
|
|
||||||
|
@ -23,7 +24,6 @@ NotificationPage::NotificationPage()
|
||||||
: SettingsPage("Notifications", ":/settings/notification2.svg")
|
: SettingsPage("Notifications", ":/settings/notification2.svg")
|
||||||
{
|
{
|
||||||
LayoutCreator<NotificationPage> layoutCreator(this);
|
LayoutCreator<NotificationPage> layoutCreator(this);
|
||||||
|
|
||||||
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin();
|
||||||
{
|
{
|
||||||
auto tabs = layout.emplace<QTabWidget>();
|
auto tabs = layout.emplace<QTabWidget>();
|
||||||
|
@ -39,6 +39,23 @@ NotificationPage::NotificationPage()
|
||||||
settings.append(
|
settings.append(
|
||||||
this->createCheckBox("Enable toasts (Windows 8 or later)",
|
this->createCheckBox("Enable toasts (Windows 8 or later)",
|
||||||
getSettings()->notificationToast));
|
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
|
#endif
|
||||||
auto customSound =
|
auto customSound =
|
||||||
layout.emplace<QHBoxLayout>().withoutMargin();
|
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
|
} // namespace chatterino
|
||||||
|
|
|
@ -15,6 +15,8 @@ public:
|
||||||
NotificationPage();
|
NotificationPage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QComboBox *createToastReactionComboBox(
|
||||||
|
std::vector<pajlada::Signals::ScopedConnection> managedConnections);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue