Merge remote-tracking branch 'origin/master' into zneix/enhancement/login-overhaul
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Suggestions or feature request
|
||||
url: https://github.com/chatterino/chatterino2/discussions/categories/ideas
|
||||
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||
- name: Help
|
||||
url: https://github.com/chatterino/chatterino2/discussions/categories/q-a
|
||||
about: Chatterino2 not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
1
.gitmodules
vendored
|
@ -1,6 +1,7 @@
|
|||
[submodule "lib/libcommuni"]
|
||||
path = lib/libcommuni
|
||||
url = https://github.com/Chatterino/libcommuni
|
||||
branch = chatterino-cmake
|
||||
[submodule "lib/qBreakpad"]
|
||||
path = lib/qBreakpad
|
||||
url = https://github.com/jiakuan/qBreakpad.git
|
||||
|
|
|
@ -10,6 +10,7 @@ FreeBSD 13.0-CURRENT.
|
|||
|
||||
1. Install build dependencies from package sources (or build from the
|
||||
ports tree): `# pkg install qt5-core qt5-multimedia qt5-svg qt5-qmake qt5-buildtools gstreamer-plugins-good boost-libs rapidjson`
|
||||
1. go into project directory
|
||||
1. create build folder `$ mkdir build && cd build`
|
||||
1. `$ qmake .. && make`
|
||||
1. Go into the project directory
|
||||
1. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
1. Proceed to compiling using the command
|
||||
`qmake .. && make`
|
||||
|
|
|
@ -4,27 +4,28 @@ Note on Qt version compatibility: If you are installing Qt from a package manage
|
|||
|
||||
## Ubuntu 18.04
|
||||
|
||||
_most likely works the same for other Debian-like distros_
|
||||
_Most likely works the same for other Debian-like distros_
|
||||
|
||||
1. Install dependencies `sudo apt install qttools5-dev qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev cmake g++`
|
||||
1. Install all of the dependencies using `sudo apt install qttools5-dev qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev libboost-filesystem-dev cmake g++`
|
||||
|
||||
### Through Qt Creator
|
||||
### Compiling through Qt Creator
|
||||
|
||||
1. Install C++ IDE Qt Creator `sudo apt install qtcreator`
|
||||
1. Install C++ IDE Qt Creator by using `sudo apt install qtcreator`
|
||||
1. Open `chatterino.pro` with Qt Creator and select build
|
||||
|
||||
### Manually
|
||||
|
||||
1. go into project directory
|
||||
1. create build folder `mkdir build && cd build`
|
||||
1. Go into the project directory
|
||||
1. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
1. Use one of the options below to compile it
|
||||
|
||||
#### Using QMake
|
||||
### Using CMake
|
||||
|
||||
1. `qmake .. && make`
|
||||
`cmake .. && make`
|
||||
|
||||
#### Using CMake
|
||||
### Using QMake
|
||||
|
||||
1. `cmake .. && make`
|
||||
`qmake .. && make`
|
||||
|
||||
## Arch Linux
|
||||
|
||||
|
@ -34,50 +35,47 @@ _most likely works the same for other Debian-like distros_
|
|||
|
||||
### Manually
|
||||
|
||||
1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg qt5-tools gst-plugins-ugly gst-plugins-good boost rapidjson pkgconf openssl cmake`
|
||||
1. go into project directory
|
||||
1. create build folder `mkdir build && cd build`
|
||||
1. Install all of the dependencies using `sudo pacman -S qt5-base qt5-multimedia qt5-svg qt5-tools gst-plugins-ugly gst-plugins-good boost rapidjson pkgconf openssl cmake`
|
||||
1. Go into the project directory
|
||||
1. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
1. Use one of the options below to compile it
|
||||
|
||||
#### Using QMake
|
||||
### Using CMake
|
||||
|
||||
1. `qmake .. && make`
|
||||
`cmake .. && make`
|
||||
|
||||
#### Using CMake
|
||||
### Using QMake
|
||||
|
||||
1. `cmake .. && make`
|
||||
`qmake .. && make`
|
||||
|
||||
## Fedora 28 and above
|
||||
|
||||
_most likely works the same for other Red Hat-like distros. Substitue `dnf` with `yum`._
|
||||
_Most likely works the same for other Red Hat-like distros. Substitute `dnf` with `yum`._
|
||||
|
||||
1. `sudo dnf install qt5-qtbase-devel qt5-qtmultimedia-devel qt5-qtsvg-devel libsecret-devel openssl-devel boost-devel cmake`
|
||||
1. go into project directory
|
||||
1. create build folder `mkdir build && cd build`
|
||||
|
||||
### Using QMake
|
||||
|
||||
1. `qmake-qt5 .. && make -j$(nproc)`
|
||||
1. Install all of the dependencies using `sudo dnf install qt5-qtbase-devel qt5-qtmultimedia-devel qt5-qtsvg-devel libsecret-devel openssl-devel boost-devel cmake`
|
||||
1. Go into the project directory
|
||||
1. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
1. Use one of the options below to compile it
|
||||
|
||||
### Using CMake
|
||||
|
||||
1. `cmake .. && make -j$(nproc)`
|
||||
`cmake .. && make -j$(nproc)`
|
||||
|
||||
### Optional dependencies
|
||||
### Using QMake
|
||||
|
||||
_`gstreamer-plugins-good` package is retired in Fedora 31, see: [rhbz#1735324](https://bugzilla.redhat.com/show_bug.cgi?id=1735324)_
|
||||
|
||||
1. `sudo dnf install gstreamer-plugins-good` _(optional: for audio output)_
|
||||
`qmake-qt5 .. && make -j$(nproc)`
|
||||
|
||||
## NixOS 18.09+
|
||||
|
||||
1. enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full pkg-config cmake`
|
||||
1. go into project directory
|
||||
1. create build folder `mkdir build && cd build`
|
||||
|
||||
### Using QMake
|
||||
|
||||
1. `qmake .. && make`
|
||||
1. Enter the development environment with all of the dependencies: `nix-shell -p openssl boost qt5.full pkg-config cmake`
|
||||
1. Go into the project directory
|
||||
1. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
1. Use one of the options below to compile it
|
||||
|
||||
### Using CMake
|
||||
|
||||
1. `cmake .. && make`
|
||||
`cmake .. && make`
|
||||
|
||||
### Using QMake
|
||||
|
||||
`qmake .. && make`
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
|
||||
#### Note - Chatterino 2 is only tested on macOS 10.14 and above - anything below that is considered unsupported. It may or may not work on earlier versions
|
||||
|
||||
1. Install Xcode and Xcode Command Line Utilites
|
||||
2. Start Xcode, settings -> Locations, activate your Command Line Tools
|
||||
1. Install Xcode and Xcode Command Line Utilities
|
||||
2. Start Xcode, go into Settings -> Locations, and activate your Command Line Tools
|
||||
3. Install brew https://brew.sh/
|
||||
4. `brew install boost openssl rapidjson`
|
||||
5. `brew install qt`
|
||||
6. Step 5 should output some directions to add qt to your path, you will need to do this for qmake
|
||||
7. Go into project directory
|
||||
8. Create build folder `mkdir build && cd build`
|
||||
9. `qmake .. && make`
|
||||
4. Install the dependencies using `brew install boost openssl rapidjson`
|
||||
5. Install Qt using `brew install qt`
|
||||
6. Step 5 should output some directions to add Qt to your path, you will need to do this for qmake
|
||||
7. Go into the project directory
|
||||
8. Create a build folder and go into it (`mkdir build && cd build`)
|
||||
9. Compile using `qmake .. && make`
|
||||
|
||||
_If you want to use cmake instead of qmake, just replace the above qmake command with cmake_
|
||||
|
||||
|
@ -21,7 +21,7 @@ If the Project does not build at this point, you might need to add additional Pa
|
|||
`brew info openssl`
|
||||
`brew info boost`
|
||||
|
||||
If brew doesn't link openssl properly then you should be able to link it yourself using those two commands:
|
||||
If brew doesn't link OpenSSL properly then you should be able to link it yourself by using these two commands:
|
||||
|
||||
- `ln -s /usr/local/opt/openssl/lib/* /usr/local/lib`
|
||||
- `ln -s /usr/local/opt/openssl/include/openssl /usr/local/include/openssl`
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Building on Windows
|
||||
|
||||
**Note that installing all the development prerequisites and libraries will require about 30 GB of free disk space. Please ensure this space is available on your `C:` drive before proceeding.**
|
||||
**Note that installing all of the development prerequisites and libraries will require about 30 GB of free disk space. Please ensure this space is available on your `C:` drive before proceeding.**
|
||||
|
||||
This guide assumes you are on a 64-bit system. You might need to manually search out alternate download links should you desire to build chatterino on a 32-bit system.
|
||||
This guide assumes you are on a 64-bit system. You might need to manually search out alternate download links should you desire to build Chatterino on a 32-bit system.
|
||||
|
||||
## Visual Studio 2019
|
||||
|
||||
|
@ -39,12 +39,12 @@ Note: This installation will take about 1.5 GB of disk space.
|
|||
|
||||
### For Qt SSL, we need OpenSSL 1.0
|
||||
|
||||
1. Download OpenSSL for windows, version `1.0.2u`: **[Download](https://slproweb.com/download/Win64OpenSSL-1_0_2u.exe)**
|
||||
1. Download OpenSSL for Windows, version `1.0.2u`: **[Download](https://slproweb.com/download/Win64OpenSSL-1_0_2u.exe)**
|
||||
2. When prompted, install it to any arbitrary empty directory.
|
||||
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory".
|
||||
4. Copy the OpenSSL 1.0 files from its `\bin` folder to `C:\local\bin` (You will need to create the folder)
|
||||
5. Then copy the OpenSSL 1.1 files from its `\bin` folder to `C:\local\bin` (Overwrite any duplicate files)
|
||||
6. Add `C:\local\bin` to your path folder ([Follow guide here if you don't know how to do it](https://www.computerhope.com/issues/ch000549.htm#windows10))
|
||||
6. Add `C:\local\bin` to your path folder ([Follow the guide here if you don't know how to do it](https://www.computerhope.com/issues/ch000549.htm#windows10))
|
||||
|
||||
**If the download links above do not work, try downloading similar 1.1.x & 1.0.x versions [here](https://slproweb.com/products/Win32OpenSSL.html). Note: Don't download the "light" installers, they do not have the required files.**
|
||||
|
||||
|
@ -82,7 +82,7 @@ Compiling with Breakpad support enables crash reports that can be of use for dev
|
|||
|
||||
## Run the build in Qt Creator
|
||||
|
||||
1. Open the `chatterino.pro` file by double-clicking or by opening it via Qt Creator.
|
||||
1. Open the `chatterino.pro` file by double-clicking it, or by opening it via Qt Creator.
|
||||
2. You will be presented with a screen that is titled "Configure Project". In this screen, you should have at least one option present ready to be configured, like this:
|
||||
![Qt Create Configure Project screenshot](https://i.imgur.com/dbz45mB.png)
|
||||
3. Select the profile(s) you want to build with and click "Configure Project".
|
||||
|
@ -105,10 +105,10 @@ To produce a standalone package, you need to generate all required files using t
|
|||
|
||||
To produce all supplement files for a standalone build, follow these steps (adjust paths as required):
|
||||
|
||||
1. Navigate to your build output directory with windows explorer, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release`
|
||||
1. Navigate to your build output directory with Windows Explorer, e.g. `C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release`
|
||||
2. Enter the `release` directory
|
||||
3. Delete all files except the `chatterino.exe` file. You should be left with a directory only containing `chatterino.exe`.
|
||||
4. Open a `cmd` window and execute:
|
||||
4. Open a command prompt and execute:
|
||||
|
||||
cd C:\Users\example\src\build-chatterino-Desktop_Qt_5_15_2_MSVC2019_64bit-Release\release
|
||||
C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe chatterino.exe
|
||||
|
@ -124,9 +124,9 @@ To produce all supplement files for a standalone build, follow these steps (adju
|
|||
|
||||
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)).
|
||||
|
||||
## Building with CMake
|
||||
## Using CMake
|
||||
|
||||
Open up your terminal with the Visual Studio environment variables, then:
|
||||
Open up your terminal with the Visual Studio environment variables, then enter the following commands:
|
||||
|
||||
1. `mkdir build`
|
||||
2. `cd build`
|
||||
|
|
21
CHANGELOG.md
|
@ -3,13 +3,34 @@
|
|||
## Unversioned
|
||||
|
||||
- Major: Changed login process. It is now fully automatic with no need to copy-paste credentials and is fully client-sided (doesn't require connecting to chatterino.com). (#3065)
|
||||
- Minor: Remove TwitchEmotes.com attribution and the open/copy options when right-clicking a Twitch Emote. (#2214, #3136)
|
||||
- Minor: Strip leading @ and trailing , from username in /user and /usercard commands. (#3143)
|
||||
- Minor: Display a system message when reloading subscription emotes to match BTTV/FFZ behavior (#3135)
|
||||
- Bugfix: Moderation mode and active filters are now preserved when opening a split as a popup. (#3113, #3130)
|
||||
- Bugfix: Fixed a bug that caused all badge highlights to use the same color. (#3132, #3134)
|
||||
- Dev: Renamed CMake's build option `USE_SYSTEM_QT5KEYCHAIN` to `USE_SYSTEM_QTKEYCHAIN`. (#3103)
|
||||
- Dev: Add benchmarks that can be compiled with the `BUILD_BENCHMARKS` CMake flag. Off by default. (#3038)
|
||||
|
||||
## 2.3.4
|
||||
|
||||
- Major: Newly uploaded Twitch emotes are once again present in emote picker and can be autocompleted with Tab as well. (#2992)
|
||||
- Major: Deprecated `/(un)follow` commands and (un)following in the usercards as Twitch has removed this feature for 3rd party applications. (#3076, #3078)
|
||||
- Major: Added the ability to add nicknames for users. (#137, #2981)
|
||||
- Major: Fixed constant disconnections with more than 20 channels by rate-limiting outgoing JOIN messages. (#3112, #3115)
|
||||
- Minor: Added autocompletion in /whispers for Twitch emotes, Global Bttv/Ffz emotes and emojis. (#2999, #3033)
|
||||
- Minor: Received Twitch messages now use the exact same timestamp (obtained from Twitch's server) for every Chatterino user instead of assuming message timestamp on client's side. (#3021)
|
||||
- Minor: Received IRC messages use `time` message tag for timestamp if it's available. (#3021)
|
||||
- Minor: Added informative messages for recent-messages API's errors. (#3029)
|
||||
- Minor: Added section with helpful Chatterino-related links to the About page. (#3068)
|
||||
- Minor: Now uses spaces instead of magic Unicode character for sending duplicate messages (#3081)
|
||||
- Minor: Added `channel.live` filter variable (#3092, #3110)
|
||||
- Bugfix: Fixed "smiley" emotes being unable to be "Tabbed" with autocompletion, introduced in v2.3.3. (#3010)
|
||||
- Bugfix: Fixed PubSub not properly trying to resolve pending listens when the pending listens list was larger than 50. (#3037)
|
||||
- Bugfix: Copy buttons in usercard now show properly in light mode (#3057)
|
||||
- Bugfix: Fixed comma appended to username completion when not at the beginning of the message. (#3060)
|
||||
- Bugfix: Fixed bug misplacing chat when zooming on Chrome with Chatterino Native Host extension (#1936)
|
||||
- Bugfix: Channel point redemptions from ignored users are now properly blocked. (#3102)
|
||||
- Dev: Allow building against Qt 5.11 (#3105)
|
||||
- Dev: Ubuntu packages are now available (#2936)
|
||||
- Dev: Disabled update checker on Flatpak. (#3051)
|
||||
- Dev: Add logging for HTTP requests (#2991)
|
||||
|
|
|
@ -7,17 +7,25 @@ list(APPEND CMAKE_MODULE_PATH
|
|||
"${CMAKE_SOURCE_DIR}/cmake/sanitizers-cmake/cmake"
|
||||
)
|
||||
|
||||
project(chatterino VERSION 2.3.3)
|
||||
project(chatterino VERSION 2.3.4)
|
||||
|
||||
option(BUILD_APP "Build Chatterino" ON)
|
||||
option(BUILD_TESTS "Build the tests for Chatterino" OFF)
|
||||
option(BUILD_BENCHMARKS "Build the benchmarks for Chatterino" OFF)
|
||||
option(USE_SYSTEM_PAJLADA_SETTINGS "Use system pajlada settings library" OFF)
|
||||
option(USE_SYSTEM_LIBCOMMUNI "Use system communi library" OFF)
|
||||
option(USE_SYSTEM_QT5KEYCHAIN "Use system Qt5Keychain library" OFF)
|
||||
option(USE_SYSTEM_QTKEYCHAIN "Use system QtKeychain library" OFF)
|
||||
option(USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
option(BUILD_WITH_QT6 "Use Qt6 instead of default Qt5" OFF)
|
||||
|
||||
option(USE_CONAN "Use conan" OFF)
|
||||
|
||||
if (BUILD_WITH_QT6)
|
||||
set(MAJOR_QT_VERSION "6")
|
||||
else()
|
||||
set(MAJOR_QT_VERSION "5")
|
||||
endif()
|
||||
|
||||
if (USE_CONAN OR CONAN_EXPORTED)
|
||||
include(${CMAKE_CURRENT_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup(TARGETS NO_OUTPUT_DIRS)
|
||||
|
@ -31,7 +39,7 @@ endif ()
|
|||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/GIT.cmake)
|
||||
|
||||
find_package(Qt5 REQUIRED
|
||||
find_package(Qt${MAJOR_QT_VERSION} REQUIRED
|
||||
COMPONENTS
|
||||
Core
|
||||
Widgets
|
||||
|
@ -73,17 +81,17 @@ endif()
|
|||
# Link QtKeychain statically
|
||||
option(QTKEYCHAIN_STATIC "" ON)
|
||||
|
||||
if (USE_SYSTEM_QT5KEYCHAIN)
|
||||
find_package(Qt5Keychain REQUIRED)
|
||||
if (USE_SYSTEM_QTKEYCHAIN)
|
||||
find_package(Qt${MAJOR_QT_VERSION}Keychain REQUIRED)
|
||||
else()
|
||||
set(QT5KEYCHAIN_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/qtkeychain")
|
||||
if (NOT EXISTS "${QT5KEYCHAIN_ROOT_LIB_FOLDER}/CMakeLists.txt")
|
||||
set(QTKEYCHAIN_ROOT_LIB_FOLDER "${CMAKE_SOURCE_DIR}/lib/qtkeychain")
|
||||
if (NOT EXISTS "${QTKEYCHAIN_ROOT_LIB_FOLDER}/CMakeLists.txt")
|
||||
message(FATAL_ERROR "Submodules probably not loaded, unable to find lib/qtkeychain/CMakeLists.txt")
|
||||
endif()
|
||||
|
||||
add_subdirectory("${QT5KEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL)
|
||||
if (NOT TARGET qt5keychain)
|
||||
message(FATAL_ERROR "qt5keychain target was not created :@")
|
||||
add_subdirectory("${QTKEYCHAIN_ROOT_LIB_FOLDER}" EXCLUDE_FROM_ALL)
|
||||
if (NOT TARGET qt${MAJOR_QT_VERSION}keychain)
|
||||
message(FATAL_ERROR "qt${MAJOR_QT_VERSION}keychain target was not created :@")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -95,6 +103,11 @@ if (BUILD_TESTS)
|
|||
find_package(GTest REQUIRED)
|
||||
endif ()
|
||||
|
||||
if (BUILD_BENCHMARKS)
|
||||
# Include system benchmark (Google Benchmark)
|
||||
find_package(benchmark REQUIRED)
|
||||
endif ()
|
||||
|
||||
find_package(PajladaSerialize REQUIRED)
|
||||
find_package(PajladaSignals REQUIRED)
|
||||
find_package(LRUCache REQUIRED)
|
||||
|
@ -112,7 +125,7 @@ endif()
|
|||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (BUILD_TESTS)
|
||||
if (BUILD_TESTS OR BUILD_BENCHMARKS)
|
||||
add_definitions(-DCHATTERINO_TEST)
|
||||
endif ()
|
||||
|
||||
|
@ -123,4 +136,8 @@ if (BUILD_TESTS)
|
|||
add_subdirectory(tests)
|
||||
endif ()
|
||||
|
||||
if (BUILD_BENCHMARKS)
|
||||
add_subdirectory(benchmarks)
|
||||
endif ()
|
||||
|
||||
feature_summary(WHAT ALL)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Chatterino code guidelines
|
||||
|
||||
This is a set of guidelines for contributing to Chatterino. The goal is to teach programmers without C++ background (java/python/etc.), people who haven't used Qt or otherwise have different experience the idioms of the codebase. Thus we will focus on those which are different from those other environments. There are extra guidelines available [here](https://hackmd.io/@fourtf/chatterino-pendantic-guidelines) but they are considered as extras and not as important.
|
||||
This is a set of guidelines for contributing to Chatterino. The goal is to teach programmers without a C++ background (java/python/etc.), people who haven't used Qt, or otherwise have different experience, the idioms of the codebase. Thus we will focus on those which are different from those other environments. There are extra guidelines available [here](https://hackmd.io/@fourtf/chatterino-pendantic-guidelines) but they are considered as extras and not as important.
|
||||
|
||||
# Tooling
|
||||
|
||||
|
@ -70,7 +70,7 @@ void myFunc() {
|
|||
|
||||
## Passing parameters
|
||||
|
||||
The way a parameter is passed signals how it is going to be used inside of the function. C++ doesn't have multiple return values so there is "out parameters" (reference to a variable that is going to be assigned inside of the function) to simulate multiple return values.
|
||||
The way a parameter is passed, signals how it is going to be used inside of the function. C++ doesn't have multiple return values, so there are "out parameters" (reference to a variable that is going to be assigned inside of the function) to simulate multiple return values.
|
||||
|
||||
**Cheap to copy types** like int/enum/etc. can be passed in per value since copying them is fast.
|
||||
|
||||
|
@ -122,11 +122,11 @@ void main() {
|
|||
}
|
||||
```
|
||||
|
||||
Generally the lowest level of requirement should be used e.g. passing `Channel&` instead of `std::shared_ptr<Channel>&` (aka `ChannelPtr`) if possible.
|
||||
Generally the lowest level of requirement should be used, e.g. passing `Channel&` instead of `std::shared_ptr<Channel>&` (aka `ChannelPtr`) if possible.
|
||||
|
||||
## Members
|
||||
|
||||
All functions names are in `camelCase`. _Private_ member variables are in `camelCase_` (note the underscore at the end). We don't use the `get` prefix for getters. We mark functions as `const` [if applicable](https://stackoverflow.com/questions/751681/meaning-of-const-last-in-a-function-declaration-of-a-class).
|
||||
All function names are in `camelCase`. _Private_ member variables are in `camelCase_` (note the underscore at the end). We don't use the `get` prefix for getters. We mark functions as `const` [if applicable](https://stackoverflow.com/questions/751681/meaning-of-const-last-in-a-function-declaration-of-a-class).
|
||||
|
||||
```cpp
|
||||
class NamedObject
|
||||
|
@ -212,6 +212,6 @@ Keep the element on the stack if possible. If you need a pointer or have complex
|
|||
|
||||
#### QObject classes
|
||||
|
||||
- Use the [object tree](https://doc.qt.io/qt-5/objecttrees.html#) to manage lifetime where possible. Objects are destroyed when their parent object is destroyed.
|
||||
- Use the [object tree](https://doc.qt.io/qt-5/objecttrees.html#) to manage lifetimes where possible. Objects are destroyed when their parent object is destroyed.
|
||||
- If you have to explicitly delete an object use `variable->deleteLater()` instead of `delete variable`. This ensures that it will be deleted on the correct thread.
|
||||
- If an object doesn't have a parent consider using `std::unique_ptr<Type, DeleteLater>` with `DeleteLater` from "src/common/Common.hpp". This will call `deleteLater()` on the pointer once it goes out of scope or the object is destroyed.
|
||||
- If an object doesn't have a parent, consider using `std::unique_ptr<Type, DeleteLater>` with `DeleteLater` from "src/common/Common.hpp". This will call `deleteLater()` on the pointer once it goes out of scope, or the object is destroyed.
|
||||
|
|
35
benchmarks/.clang-format
Normal file
|
@ -0,0 +1,35 @@
|
|||
Language: Cpp
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLambdasOnASingleLine: Empty
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
BasedOnStyle: Google
|
||||
BraceWrapping: {
|
||||
AfterClass: 'true'
|
||||
AfterControlStatement: 'true'
|
||||
AfterFunction: 'true'
|
||||
AfterNamespace: 'false'
|
||||
BeforeCatch: 'true'
|
||||
BeforeElse: 'true'
|
||||
}
|
||||
BreakBeforeBraces: Custom
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
ColumnLimit: 80
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
DerivePointerBinding: false
|
||||
FixNamespaceComments: true
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
IndentPPDirectives: AfterHash
|
||||
IncludeBlocks: Preserve
|
||||
NamespaceIndentation: Inner
|
||||
PointerBindsToType: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
Standard: Auto
|
||||
ReflowComments: false
|
28
benchmarks/CMakeLists.txt
Normal file
|
@ -0,0 +1,28 @@
|
|||
project(chatterino-benchmark)
|
||||
|
||||
set(benchmark_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
||||
# Add your new file above this line!
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${benchmark_SOURCES})
|
||||
add_sanitizers(${PROJECT_NAME})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-lib)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
CHATTERINO_TEST
|
||||
)
|
||||
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
57
benchmarks/src/Emojis.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "providers/emoji/Emojis.hpp"
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
static void BM_ShortcodeParsing(benchmark::State &state)
|
||||
{
|
||||
Emojis emojis;
|
||||
|
||||
emojis.load();
|
||||
|
||||
struct TestCase {
|
||||
QString input;
|
||||
QString expectedOutput;
|
||||
};
|
||||
|
||||
std::vector<TestCase> tests{
|
||||
{
|
||||
// input
|
||||
"foo :penguin: bar",
|
||||
// expected output
|
||||
"foo 🐧 bar",
|
||||
},
|
||||
{
|
||||
// input
|
||||
"foo :nonexistantcode: bar",
|
||||
// expected output
|
||||
"foo :nonexistantcode: bar",
|
||||
},
|
||||
{
|
||||
// input
|
||||
":male-doctor:",
|
||||
// expected output
|
||||
"👨⚕️",
|
||||
},
|
||||
};
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto &test : tests)
|
||||
{
|
||||
auto output = emojis.replaceShortCodes(test.input);
|
||||
|
||||
auto matches = output == test.expectedOutput;
|
||||
if (!matches && !output.endsWith(QChar(0xFE0F)))
|
||||
{
|
||||
// Try to append 0xFE0F if needed
|
||||
output = output.append(QChar(0xFE0F));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_ShortcodeParsing);
|
18
benchmarks/src/main.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include <benchmark/benchmark.h>
|
||||
#include <QApplication>
|
||||
#include <QtConcurrent>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
::benchmark::Initialize(&argc, argv);
|
||||
|
||||
QtConcurrent::run([&app] {
|
||||
::benchmark::RunSpecifiedBenchmarks();
|
||||
|
||||
app.exit(0);
|
||||
});
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -14,7 +14,7 @@ CCACHE_BIN = $$system(which ccache)
|
|||
CONFIG+=ccache
|
||||
}
|
||||
|
||||
MINIMUM_REQUIRED_QT_VERSION = 5.12.0
|
||||
MINIMUM_REQUIRED_QT_VERSION = 5.11.0
|
||||
|
||||
!versionAtLeast(QT_VERSION, $$MINIMUM_REQUIRED_QT_VERSION) {
|
||||
error("You're trying to compile with Qt $$QT_VERSION, but minimum required Qt version is $$MINIMUM_REQUIRED_QT_VERSION")
|
||||
|
@ -159,9 +159,11 @@ SOURCES += \
|
|||
src/controllers/highlights/HighlightModel.cpp \
|
||||
src/controllers/highlights/HighlightPhrase.cpp \
|
||||
src/controllers/highlights/UserHighlightModel.cpp \
|
||||
src/controllers/ignores/IgnoreController.cpp \
|
||||
src/controllers/ignores/IgnoreModel.cpp \
|
||||
src/controllers/moderationactions/ModerationAction.cpp \
|
||||
src/controllers/moderationactions/ModerationActionModel.cpp \
|
||||
src/controllers/nicknames/NicknamesModel.cpp \
|
||||
src/controllers/notifications/NotificationController.cpp \
|
||||
src/controllers/notifications/NotificationModel.cpp \
|
||||
src/controllers/pings/MutedChannelModel.cpp \
|
||||
|
@ -250,6 +252,7 @@ SOURCES += \
|
|||
src/util/LayoutHelper.cpp \
|
||||
src/util/NuulsUploader.cpp \
|
||||
src/util/RapidjsonHelpers.cpp \
|
||||
src/util/RatelimitBucket.cpp \
|
||||
src/util/SplitCommand.cpp \
|
||||
src/util/StreamerMode.cpp \
|
||||
src/util/StreamLink.cpp \
|
||||
|
@ -316,6 +319,7 @@ SOURCES += \
|
|||
src/widgets/settingspages/IgnoresPage.cpp \
|
||||
src/widgets/settingspages/KeyboardSettingsPage.cpp \
|
||||
src/widgets/settingspages/ModerationPage.cpp \
|
||||
src/widgets/settingspages/NicknamesPage.cpp \
|
||||
src/widgets/settingspages/NotificationPage.cpp \
|
||||
src/widgets/settingspages/SettingsPage.cpp \
|
||||
src/widgets/splits/ClosedSplits.cpp \
|
||||
|
@ -392,6 +396,8 @@ HEADERS += \
|
|||
src/controllers/ignores/IgnorePhrase.hpp \
|
||||
src/controllers/moderationactions/ModerationAction.hpp \
|
||||
src/controllers/moderationactions/ModerationActionModel.hpp \
|
||||
src/controllers/nicknames/Nickname.hpp \
|
||||
src/controllers/nicknames/NicknamesModel.hpp \
|
||||
src/controllers/notifications/NotificationController.hpp \
|
||||
src/controllers/notifications/NotificationModel.hpp \
|
||||
src/controllers/pings/MutedChannelModel.hpp \
|
||||
|
@ -505,6 +511,7 @@ HEADERS += \
|
|||
src/util/rangealgorithm.hpp \
|
||||
src/util/RapidjsonHelpers.hpp \
|
||||
src/util/RapidJsonSerializeQString.hpp \
|
||||
src/util/RatelimitBucket.hpp \
|
||||
src/util/RemoveScrollAreaBackground.hpp \
|
||||
src/util/SampleCheerMessages.hpp \
|
||||
src/util/SampleLinks.hpp \
|
||||
|
@ -580,6 +587,7 @@ HEADERS += \
|
|||
src/widgets/settingspages/IgnoresPage.hpp \
|
||||
src/widgets/settingspages/KeyboardSettingsPage.hpp \
|
||||
src/widgets/settingspages/ModerationPage.hpp \
|
||||
src/widgets/settingspages/NicknamesPage.hpp \
|
||||
src/widgets/settingspages/NotificationPage.hpp \
|
||||
src/widgets/settingspages/SettingsPage.hpp \
|
||||
src/widgets/splits/ClosedSplits.hpp \
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[requires]
|
||||
openssl/1.1.1d
|
||||
boost/1.75.0
|
||||
openssl/1.1.1k
|
||||
boost/1.76.0
|
||||
|
||||
[generators]
|
||||
qmake
|
||||
|
|
88
docs/test-and-benchmark.md
Normal file
|
@ -0,0 +1,88 @@
|
|||
# Test and Benchmark
|
||||
|
||||
Chatterino includes a set of unit tests and benchmarks. These can be built using cmake by adding the `-DBUILD_TESTS=On` and `-DBUILD_BENCHMARKS=On` flags respectively.
|
||||
|
||||
## Adding your own test
|
||||
|
||||
1. Create a new file for the file you're adding tests for. If you're creating tests for `src/providers/emoji/Emojis.cpp`, create `tests/src/Emojis.cpp`.
|
||||
2. Add the newly created file to `tests/CMakeLists.txt` in the `test_SOURCES` variable (see the comment near it)
|
||||
|
||||
See `tests/src/Emojis.cpp` for simple tests you can base your tests off of.
|
||||
|
||||
Read up on http://google.github.io/googletest/primer.html to figure out how GoogleTest works.
|
||||
|
||||
## Building and running tests
|
||||
|
||||
```sh
|
||||
mkdir build-tests
|
||||
cd build-tests
|
||||
cmake -DBUILD_TESTS=On ..
|
||||
make
|
||||
./bin/chatterino-test
|
||||
```
|
||||
|
||||
### Example output
|
||||
|
||||
```
|
||||
[==========] Running 26 tests from 8 test suites.
|
||||
[----------] Global test environment set-up.
|
||||
[----------] 2 tests from AccessGuardLocker
|
||||
[ RUN ] AccessGuardLocker.NonConcurrentUsage
|
||||
[ OK ] AccessGuardLocker.NonConcurrentUsage (0 ms)
|
||||
[ RUN ] AccessGuardLocker.ConcurrentUsage
|
||||
[ OK ] AccessGuardLocker.ConcurrentUsage (686 ms)
|
||||
[----------] 2 tests from AccessGuardLocker (686 ms total)
|
||||
|
||||
[----------] 4 tests from NetworkCommon
|
||||
[ RUN ] NetworkCommon.parseHeaderList1
|
||||
[ OK ] NetworkCommon.parseHeaderList1 (0 ms)
|
||||
[ RUN ] NetworkCommon.parseHeaderListTrimmed
|
||||
[ OK ] NetworkCommon.parseHeaderListTrimmed (0 ms)
|
||||
[ RUN ] NetworkCommon.parseHeaderListColonInValue
|
||||
...
|
||||
[ RUN ] TwitchAccount.NotEnoughForMoreThanOneBatch
|
||||
[ OK ] TwitchAccount.NotEnoughForMoreThanOneBatch (0 ms)
|
||||
[ RUN ] TwitchAccount.BatchThreeParts
|
||||
[ OK ] TwitchAccount.BatchThreeParts (0 ms)
|
||||
[----------] 3 tests from TwitchAccount (2 ms total)
|
||||
|
||||
[----------] Global test environment tear-down
|
||||
[==========] 26 tests from 8 test suites ran. (10297 ms total)
|
||||
[ PASSED ] 26 tests.
|
||||
```
|
||||
|
||||
## Adding your own benchmark
|
||||
|
||||
1. Create a new file for the file you're adding benchmark for. If you're creating benchmarks for `src/providers/emoji/Emojis.cpp`, create `benchmarks/src/Emojis.cpp`.
|
||||
2. Add the newly created file to `benchmarks/CMakeLists.txt` in the `benchmark_SOURCES` variable (see the comment near it)
|
||||
|
||||
See `benchmarks/src/Emojis.cpp` for simple benchmark you can base your benchmarks off of.
|
||||
|
||||
## Building and running benchmarks
|
||||
|
||||
```sh
|
||||
mkdir build-benchmarks
|
||||
cd build-benchmarks
|
||||
cmake -DBUILD_BENCHMARKS=On ..
|
||||
make
|
||||
./bin/chatterino-benchmark
|
||||
```
|
||||
|
||||
### Example output
|
||||
|
||||
```
|
||||
2021-07-18T13:12:11+02:00
|
||||
Running ./bin/chatterino-benchmark
|
||||
Run on (12 X 4000 MHz CPU s)
|
||||
CPU Caches:
|
||||
L1 Data 32 KiB (x6)
|
||||
L1 Instruction 32 KiB (x6)
|
||||
L2 Unified 256 KiB (x6)
|
||||
L3 Unified 15360 KiB (x1)
|
||||
Load Average: 2.86, 3.08, 3.51
|
||||
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
|
||||
--------------------------------------------------------------
|
||||
Benchmark Time CPU Iterations
|
||||
--------------------------------------------------------------
|
||||
BM_ShortcodeParsing 2394 ns 2389 ns 278933
|
||||
```
|
|
@ -1 +1 @@
|
|||
Subproject commit ef8daa14946b8e19f536200e28db2b25e8311ba5
|
||||
Subproject commit 95f05478de1623767282d8019ea8f3a4b1178b35
|
BIN
resources/avatars/alazymeme.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
resources/avatars/xheaveny.png
Normal file
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1,004 B |
|
@ -9,5 +9,5 @@
|
|||
height="368.64pt"
|
||||
viewBox="0 0 368.64 368.64">
|
||||
<defs/>
|
||||
<path id="shape0" transform="translate(0.477297082745412, 59.3121166947556)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-opacity="0" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" d="M42.034 0.254558L43.0522 255.449L43.6262 256.827L44.1855 258.216L44.7441 259.606L45.3161 260.985L45.9154 262.341L46.5562 263.662L47.2523 264.937L48.018 266.154L48.867 267.3L49.8136 268.365L50.8717 269.336L52.0553 270.202L53.3785 270.951L54.8553 271.571L56.4997 272.05L58.3257 272.378L268.973 272.505L269.227 294.397L268.9 295.844L268.498 297.271L268.017 298.668L267.453 300.026L266.799 301.334L266.053 302.584L265.207 303.766L264.259 304.87L263.202 305.887L262.031 306.807L260.742 307.62L259.33 308.318L257.791 308.89L256.118 309.328L13.2689 309.328L11.4859 308.872L9.86178 308.304L8.38902 307.628L7.05996 306.85L5.86701 305.976L4.80256 305.011L3.85899 303.959L3.02871 302.828L2.3041 301.621L1.67756 300.345L1.14149 299.004L0.68828 297.605L0.310317 296.152L0 294.651L0.310203 14.5104L0.847067 13.1388L1.38231 11.7663L1.93671 10.4058L2.53105 9.0706L3.18609 7.77381L3.92263 6.52858L4.76143 5.34805L5.72327 4.24536L6.82893 3.23365L8.09918 2.32608L9.55481 1.53579L11.2166 0.87591L13.1053 0.3596L15.2417 0L42.034 0.254558"/><path id="shape1" transform="translate(61.0200045914134, 1.04007141771575)" fill="#f2f2f2" fill-rule="evenodd" d="M270.415 114.679L270.415 288.287L269.98 289.646L269.61 291.035L269.286 292.446L268.992 293.865L268.712 295.284L268.428 296.691L268.123 298.076L267.781 299.428L267.386 300.735L266.919 301.989L266.364 303.177L265.704 304.288L264.923 305.314L264.003 306.241L262.928 307.061L261.681 307.762L260.244 308.333L258.602 308.764L256.737 309.043L254.632 309.161L16.8749 309.798L14.0545 309.696L11.5994 309.354L9.48192 308.797L7.67441 308.049L6.14912 307.134L4.87835 306.076L3.8344 304.9L2.98956 303.63L2.31611 302.29L1.78637 300.905L1.37262 299.499L1.04715 298.095L0.78226 296.719L0.550247 295.394L0.323402 294.145L0.0740195 292.997L0 16.0599L0.288877 13.6625L0.719498 11.5587L1.28146 9.72701L1.96438 8.14583L2.75785 6.79365L3.65147 5.64893L4.63485 4.69013L5.69758 3.89572L6.82928 3.24416L8.01954 2.7139L9.25797 2.28343L10.5342 1.93118L11.8377 1.63564L13.1583 1.37525L14.4854 1.12849L15.8087 0.87381L17.1178 0.58968L18.4022 0.254558L138.172 0L138.045 97.4959L138.594 99.0696L139.153 100.628L139.731 102.16L140.334 103.656L140.97 105.104L141.646 106.495L142.369 107.817L143.147 109.06L143.987 110.213L144.896 111.266L145.883 112.208L146.954 113.028L148.116 113.716L149.377 114.261L150.744 114.653L152.225 114.88L153.827 114.933L191.897 114.85L249.265 114.725L270.415 114.679"/><path id="shape2" transform="translate(210.201627880834, 1.9091882641311)" fill="#ffffff" fill-rule="evenodd" d="M0.0636396 0L121.743 103.223L14.828 103.478C6.29306 101.355 0.80087 96.9823 0.0636396 89.0955C-0.0212132 88.5863 -0.0212132 58.8879 0.0636396 0Z"/>
|
||||
<path id="shape0" transform="translate(0.477297082745412, 59.3121166947556)" fill="#000000" fill-rule="evenodd" d="M42.034 0.254558L43.0522 255.449L43.6262 256.827L44.1855 258.216L44.7441 259.606L45.3161 260.985L45.9154 262.341L46.5562 263.662L47.2523 264.937L48.018 266.154L48.867 267.3L49.8136 268.365L50.8717 269.336L52.0553 270.202L53.3785 270.951L54.8553 271.571L56.4997 272.05L58.3257 272.378L268.973 272.505L269.227 294.397L268.9 295.844L268.498 297.271L268.017 298.668L267.453 300.026L266.799 301.334L266.053 302.584L265.207 303.766L264.259 304.87L263.202 305.887L262.031 306.807L260.742 307.62L259.33 308.318L257.791 308.89L256.118 309.328L13.2689 309.328L11.4859 308.872L9.86178 308.304L8.38902 307.628L7.05996 306.85L5.86701 305.976L4.80256 305.011L3.85899 303.959L3.02871 302.828L2.3041 301.621L1.67756 300.345L1.14149 299.004L0.68828 297.605L0.310317 296.152L0 294.651L0.310203 14.5104L0.847067 13.1388L1.38231 11.7663L1.93671 10.4058L2.53105 9.0706L3.18609 7.77381L3.92263 6.52858L4.76143 5.34805L5.72327 4.24536L6.82893 3.23365L8.09918 2.32608L9.55481 1.53579L11.2166 0.87591L13.1053 0.3596L15.2417 0L42.034 0.254558"/><path id="shape1" transform="translate(61.0200045914134, 1.04007141771575)" fill="#000000" fill-rule="evenodd" d="M270.415 114.679L270.415 288.287L269.98 289.646L269.61 291.035L269.286 292.446L268.992 293.865L268.712 295.284L268.428 296.691L268.123 298.076L267.781 299.428L267.386 300.735L266.919 301.989L266.364 303.177L265.704 304.288L264.923 305.314L264.003 306.241L262.928 307.061L261.681 307.762L260.244 308.333L258.602 308.764L256.737 309.043L254.632 309.161L16.8749 309.798L14.0545 309.696L11.5994 309.354L9.48192 308.797L7.67441 308.049L6.14912 307.134L4.87835 306.076L3.8344 304.9L2.98956 303.63L2.31611 302.29L1.78637 300.905L1.37262 299.499L1.04715 298.095L0.78226 296.719L0.550247 295.394L0.323402 294.145L0.0740195 292.997L0 16.0599L0.288877 13.6625L0.719498 11.5587L1.28146 9.72701L1.96438 8.14583L2.75785 6.79365L3.65147 5.64893L4.63485 4.69013L5.69758 3.89572L6.82928 3.24416L8.01954 2.7139L9.25797 2.28343L10.5342 1.93118L11.8377 1.63564L13.1583 1.37525L14.4854 1.12849L15.8087 0.87381L17.1178 0.58968L18.4022 0.254558L138.172 0L138.045 97.4959L138.594 99.0696L139.153 100.628L139.731 102.16L140.334 103.656L140.97 105.104L141.646 106.495L142.369 107.817L143.147 109.06L143.987 110.213L144.896 111.266L145.883 112.208L146.954 113.028L148.116 113.716L149.377 114.261L150.744 114.653L152.225 114.88L153.827 114.933L191.897 114.85L249.265 114.725L270.415 114.679"/><path id="shape2" transform="translate(210.201627880834, 1.9091882641311)" fill="#000000" fill-rule="evenodd" d="M0.0636396 0L121.743 103.223L14.828 103.478C6.29306 101.355 0.80087 96.9823 0.0636396 89.0955C-0.0212132 88.5863 -0.0212132 58.8879 0.0636396 0Z"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1,004 B After Width: | Height: | Size: 1.1 KiB |
|
@ -9,5 +9,5 @@
|
|||
height="368.64pt"
|
||||
viewBox="0 0 368.64 368.64">
|
||||
<defs/>
|
||||
<path id="shape0" transform="translate(0.477297082745412, 59.3121166947556)" fill="#000000" fill-rule="evenodd" d="M42.034 0.254558L43.0522 255.449L43.6262 256.827L44.1855 258.216L44.7441 259.606L45.3161 260.985L45.9154 262.341L46.5562 263.662L47.2523 264.937L48.018 266.154L48.867 267.3L49.8136 268.365L50.8717 269.336L52.0553 270.202L53.3785 270.951L54.8553 271.571L56.4997 272.05L58.3257 272.378L268.973 272.505L269.227 294.397L268.9 295.844L268.498 297.271L268.017 298.668L267.453 300.026L266.799 301.334L266.053 302.584L265.207 303.766L264.259 304.87L263.202 305.887L262.031 306.807L260.742 307.62L259.33 308.318L257.791 308.89L256.118 309.328L13.2689 309.328L11.4859 308.872L9.86178 308.304L8.38902 307.628L7.05996 306.85L5.86701 305.976L4.80256 305.011L3.85899 303.959L3.02871 302.828L2.3041 301.621L1.67756 300.345L1.14149 299.004L0.68828 297.605L0.310317 296.152L0 294.651L0.310203 14.5104L0.847067 13.1388L1.38231 11.7663L1.93671 10.4058L2.53105 9.0706L3.18609 7.77381L3.92263 6.52858L4.76143 5.34805L5.72327 4.24536L6.82893 3.23365L8.09918 2.32608L9.55481 1.53579L11.2166 0.87591L13.1053 0.3596L15.2417 0L42.034 0.254558"/><path id="shape1" transform="translate(61.0200045914134, 1.04007141771575)" fill="#000000" fill-rule="evenodd" d="M270.415 114.679L270.415 288.287L269.98 289.646L269.61 291.035L269.286 292.446L268.992 293.865L268.712 295.284L268.428 296.691L268.123 298.076L267.781 299.428L267.386 300.735L266.919 301.989L266.364 303.177L265.704 304.288L264.923 305.314L264.003 306.241L262.928 307.061L261.681 307.762L260.244 308.333L258.602 308.764L256.737 309.043L254.632 309.161L16.8749 309.798L14.0545 309.696L11.5994 309.354L9.48192 308.797L7.67441 308.049L6.14912 307.134L4.87835 306.076L3.8344 304.9L2.98956 303.63L2.31611 302.29L1.78637 300.905L1.37262 299.499L1.04715 298.095L0.78226 296.719L0.550247 295.394L0.323402 294.145L0.0740195 292.997L0 16.0599L0.288877 13.6625L0.719498 11.5587L1.28146 9.72701L1.96438 8.14583L2.75785 6.79365L3.65147 5.64893L4.63485 4.69013L5.69758 3.89572L6.82928 3.24416L8.01954 2.7139L9.25797 2.28343L10.5342 1.93118L11.8377 1.63564L13.1583 1.37525L14.4854 1.12849L15.8087 0.87381L17.1178 0.58968L18.4022 0.254558L138.172 0L138.045 97.4959L138.594 99.0696L139.153 100.628L139.731 102.16L140.334 103.656L140.97 105.104L141.646 106.495L142.369 107.817L143.147 109.06L143.987 110.213L144.896 111.266L145.883 112.208L146.954 113.028L148.116 113.716L149.377 114.261L150.744 114.653L152.225 114.88L153.827 114.933L191.897 114.85L249.265 114.725L270.415 114.679"/><path id="shape2" transform="translate(210.201627880834, 1.9091882641311)" fill="#000000" fill-rule="evenodd" d="M0.0636396 0L121.743 103.223L14.828 103.478C6.29306 101.355 0.80087 96.9823 0.0636396 89.0955C-0.0212132 88.5863 -0.0212132 58.8879 0.0636396 0Z"/>
|
||||
<path id="shape0" transform="translate(0.477297082745412, 59.3121166947556)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-opacity="0" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" d="M42.034 0.254558L43.0522 255.449L43.6262 256.827L44.1855 258.216L44.7441 259.606L45.3161 260.985L45.9154 262.341L46.5562 263.662L47.2523 264.937L48.018 266.154L48.867 267.3L49.8136 268.365L50.8717 269.336L52.0553 270.202L53.3785 270.951L54.8553 271.571L56.4997 272.05L58.3257 272.378L268.973 272.505L269.227 294.397L268.9 295.844L268.498 297.271L268.017 298.668L267.453 300.026L266.799 301.334L266.053 302.584L265.207 303.766L264.259 304.87L263.202 305.887L262.031 306.807L260.742 307.62L259.33 308.318L257.791 308.89L256.118 309.328L13.2689 309.328L11.4859 308.872L9.86178 308.304L8.38902 307.628L7.05996 306.85L5.86701 305.976L4.80256 305.011L3.85899 303.959L3.02871 302.828L2.3041 301.621L1.67756 300.345L1.14149 299.004L0.68828 297.605L0.310317 296.152L0 294.651L0.310203 14.5104L0.847067 13.1388L1.38231 11.7663L1.93671 10.4058L2.53105 9.0706L3.18609 7.77381L3.92263 6.52858L4.76143 5.34805L5.72327 4.24536L6.82893 3.23365L8.09918 2.32608L9.55481 1.53579L11.2166 0.87591L13.1053 0.3596L15.2417 0L42.034 0.254558"/><path id="shape1" transform="translate(61.0200045914134, 1.04007141771575)" fill="#f2f2f2" fill-rule="evenodd" d="M270.415 114.679L270.415 288.287L269.98 289.646L269.61 291.035L269.286 292.446L268.992 293.865L268.712 295.284L268.428 296.691L268.123 298.076L267.781 299.428L267.386 300.735L266.919 301.989L266.364 303.177L265.704 304.288L264.923 305.314L264.003 306.241L262.928 307.061L261.681 307.762L260.244 308.333L258.602 308.764L256.737 309.043L254.632 309.161L16.8749 309.798L14.0545 309.696L11.5994 309.354L9.48192 308.797L7.67441 308.049L6.14912 307.134L4.87835 306.076L3.8344 304.9L2.98956 303.63L2.31611 302.29L1.78637 300.905L1.37262 299.499L1.04715 298.095L0.78226 296.719L0.550247 295.394L0.323402 294.145L0.0740195 292.997L0 16.0599L0.288877 13.6625L0.719498 11.5587L1.28146 9.72701L1.96438 8.14583L2.75785 6.79365L3.65147 5.64893L4.63485 4.69013L5.69758 3.89572L6.82928 3.24416L8.01954 2.7139L9.25797 2.28343L10.5342 1.93118L11.8377 1.63564L13.1583 1.37525L14.4854 1.12849L15.8087 0.87381L17.1178 0.58968L18.4022 0.254558L138.172 0L138.045 97.4959L138.594 99.0696L139.153 100.628L139.731 102.16L140.334 103.656L140.97 105.104L141.646 106.495L142.369 107.817L143.147 109.06L143.987 110.213L144.896 111.266L145.883 112.208L146.954 113.028L148.116 113.716L149.377 114.261L150.744 114.653L152.225 114.88L153.827 114.933L191.897 114.85L249.265 114.725L270.415 114.679"/><path id="shape2" transform="translate(210.201627880834, 1.9091882641311)" fill="#ffffff" fill-rule="evenodd" d="M0.0636396 0L121.743 103.223L14.828 103.478C6.29306 101.355 0.80087 96.9823 0.0636396 89.0955C-0.0212132 88.5863 -0.0212132 58.8879 0.0636396 0Z"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.3 KiB |
|
@ -32,6 +32,6 @@
|
|||
<binary>chatterino</binary>
|
||||
</provides>
|
||||
<releases>
|
||||
<release version="2.3.3" date="2021-06-21"/>
|
||||
<release version="2.3.4" date="2021-08-05"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
|
|
@ -43,6 +43,8 @@ matthewde | https://github.com/m4tthewde | :/avatars/matthewde.jpg | Contributor
|
|||
Karar Al-Remahy | https://github.com/KararTY | :/avatars/kararty.png | Contributor
|
||||
Talen | https://github.com/talneoran | | Contributor
|
||||
SLCH | https://github.com/SLCH | :/avatars/slch.png | Contributor
|
||||
ALazyMeme | https://github.com/alazymeme | :/avatars/alazymeme.png | Contributor
|
||||
xHeaveny_ | https://github.com/xHeaveny | :/avatars/xheaveny.png | Contributor
|
||||
|
||||
# If you are a contributor add yourself above this line
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>auth.html</file>
|
||||
<file>avatars/alazymeme.png</file>
|
||||
<file>avatars/fourtf.png</file>
|
||||
<file>avatars/kararty.png</file>
|
||||
<file>avatars/matthewde.jpg</file>
|
||||
|
@ -8,6 +9,7 @@
|
|||
<file>avatars/pajlada.png</file>
|
||||
<file>avatars/revolter.jpg</file>
|
||||
<file>avatars/slch.png</file>
|
||||
<file>avatars/xheaveny.png</file>
|
||||
<file>avatars/zneix.png</file>
|
||||
<file>buttons/addSplit.png</file>
|
||||
<file>buttons/addSplitDark.png</file>
|
||||
|
|
|
@ -88,6 +88,8 @@ set(SOURCE_FILES
|
|||
controllers/highlights/UserHighlightModel.cpp
|
||||
controllers/highlights/UserHighlightModel.hpp
|
||||
|
||||
controllers/ignores/IgnoreController.cpp
|
||||
controllers/ignores/IgnoreController.hpp
|
||||
controllers/ignores/IgnoreModel.cpp
|
||||
controllers/ignores/IgnoreModel.hpp
|
||||
|
||||
|
@ -96,6 +98,10 @@ set(SOURCE_FILES
|
|||
controllers/moderationactions/ModerationActionModel.cpp
|
||||
controllers/moderationactions/ModerationActionModel.hpp
|
||||
|
||||
controllers/nicknames/NicknamesModel.cpp
|
||||
controllers/nicknames/NicknamesModel.hpp
|
||||
controllers/nicknames/Nickname.hpp
|
||||
|
||||
controllers/notifications/NotificationController.cpp
|
||||
controllers/notifications/NotificationController.hpp
|
||||
controllers/notifications/NotificationModel.cpp
|
||||
|
@ -286,6 +292,8 @@ set(SOURCE_FILES
|
|||
util/NuulsUploader.hpp
|
||||
util/RapidjsonHelpers.cpp
|
||||
util/RapidjsonHelpers.hpp
|
||||
util/RatelimitBucket.cpp
|
||||
util/RatelimitBucket.hpp
|
||||
util/SplitCommand.cpp
|
||||
util/SplitCommand.hpp
|
||||
util/StreamLink.cpp
|
||||
|
@ -430,6 +438,8 @@ set(SOURCE_FILES
|
|||
widgets/settingspages/KeyboardSettingsPage.hpp
|
||||
widgets/settingspages/ModerationPage.cpp
|
||||
widgets/settingspages/ModerationPage.hpp
|
||||
widgets/settingspages/NicknamesPage.cpp
|
||||
widgets/settingspages/NicknamesPage.hpp
|
||||
widgets/settingspages/NotificationPage.cpp
|
||||
widgets/settingspages/NotificationPage.hpp
|
||||
widgets/settingspages/SettingsPage.cpp
|
||||
|
@ -478,17 +488,17 @@ add_library(${LIBRARY_PROJECT} OBJECT ${SOURCE_FILES})
|
|||
|
||||
target_link_libraries(${LIBRARY_PROJECT}
|
||||
PUBLIC
|
||||
Qt5::Core
|
||||
Qt5::Widgets
|
||||
Qt5::Gui
|
||||
Qt5::Network
|
||||
Qt5::Multimedia
|
||||
Qt5::Svg
|
||||
Qt5::Concurrent
|
||||
Qt5::HttpServer
|
||||
Qt${MAJOR_QT_VERSION}::Core
|
||||
Qt${MAJOR_QT_VERSION}::Widgets
|
||||
Qt${MAJOR_QT_VERSION}::Gui
|
||||
Qt${MAJOR_QT_VERSION}::Network
|
||||
Qt${MAJOR_QT_VERSION}::Multimedia
|
||||
Qt${MAJOR_QT_VERSION}::Svg
|
||||
Qt${MAJOR_QT_VERSION}::Concurrent
|
||||
Qt${MAJOR_QT_VERSION}::HttpServer
|
||||
|
||||
LibCommuni::LibCommuni
|
||||
qt5keychain
|
||||
qt${MAJOR_QT_VERSION}keychain
|
||||
Pajlada::Serialize
|
||||
Pajlada::Settings
|
||||
Pajlada::Signals
|
||||
|
@ -517,8 +527,8 @@ if (BUILD_APP)
|
|||
)
|
||||
|
||||
if (MSVC)
|
||||
get_target_property(Qt5_Core_Location Qt5::Core LOCATION)
|
||||
get_filename_component(QT_BIN_DIR ${Qt5_Core_Location} DIRECTORY)
|
||||
get_target_property(Qt_Core_Location Qt${MAJOR_QT_VERSION}::Core LOCATION)
|
||||
get_filename_component(QT_BIN_DIR ${Qt_Core_Location} DIRECTORY)
|
||||
set(WINDEPLOYQT_COMMAND "${QT_BIN_DIR}/windeployqt.exe" $<TARGET_FILE:${EXECUTABLE_PROJECT}> --release --no-compiler-runtime --no-translations --no-opengl-sw)
|
||||
|
||||
install(TARGETS ${EXECUTABLE_PROJECT}
|
||||
|
@ -575,7 +585,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
|||
CHATTERINO_GIT_RELEASE=\"${GIT_RELEASE}\"
|
||||
CHATTERINO_GIT_COMMIT=\"${GIT_COMMIT}\"
|
||||
)
|
||||
if (USE_SYSTEM_QT5KEYCHAIN)
|
||||
if (USE_SYSTEM_QTKEYCHAIN)
|
||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||
CMAKE_BUILD
|
||||
)
|
||||
|
|
|
@ -4,11 +4,13 @@ namespace chatterino {
|
|||
|
||||
Resources2::Resources2()
|
||||
{
|
||||
this->avatars.alazymeme = QPixmap(":/avatars/alazymeme.png");
|
||||
this->avatars.fourtf = QPixmap(":/avatars/fourtf.png");
|
||||
this->avatars.kararty = QPixmap(":/avatars/kararty.png");
|
||||
this->avatars.mm2pl = QPixmap(":/avatars/mm2pl.png");
|
||||
this->avatars.pajlada = QPixmap(":/avatars/pajlada.png");
|
||||
this->avatars.slch = QPixmap(":/avatars/slch.png");
|
||||
this->avatars.xheaveny = QPixmap(":/avatars/xheaveny.png");
|
||||
this->avatars.zneix = QPixmap(":/avatars/zneix.png");
|
||||
this->buttons.addSplit = QPixmap(":/buttons/addSplit.png");
|
||||
this->buttons.addSplitDark = QPixmap(":/buttons/addSplitDark.png");
|
||||
|
|
|
@ -9,11 +9,13 @@ public:
|
|||
Resources2();
|
||||
|
||||
struct {
|
||||
QPixmap alazymeme;
|
||||
QPixmap fourtf;
|
||||
QPixmap kararty;
|
||||
QPixmap mm2pl;
|
||||
QPixmap pajlada;
|
||||
QPixmap slch;
|
||||
QPixmap xheaveny;
|
||||
QPixmap zneix;
|
||||
} avatars;
|
||||
struct {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define CHATTERINO_VERSION "2.3.3"
|
||||
#define CHATTERINO_VERSION "2.3.4"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
# define CHATTERINO_OS "win"
|
||||
|
|
|
@ -70,6 +70,32 @@ static const QStringList twitchDefaultCommands{
|
|||
|
||||
static const QStringList whisperCommands{"/w", ".w"};
|
||||
|
||||
// stripUserName removes any @ prefix or , suffix to make it more suitable for command use
|
||||
void stripUserName(QString &userName)
|
||||
{
|
||||
if (userName.startsWith('@'))
|
||||
{
|
||||
userName.remove(0, 1);
|
||||
}
|
||||
if (userName.endsWith(','))
|
||||
{
|
||||
userName.chop(1);
|
||||
}
|
||||
}
|
||||
|
||||
// stripChannelName removes any @ prefix or , suffix to make it more suitable for command use
|
||||
void stripChannelName(QString &channelName)
|
||||
{
|
||||
if (channelName.startsWith('@') || channelName.startsWith('#'))
|
||||
{
|
||||
channelName.remove(0, 1);
|
||||
}
|
||||
if (channelName.endsWith(','))
|
||||
{
|
||||
channelName.chop(1);
|
||||
}
|
||||
}
|
||||
|
||||
void sendWhisperMessage(const QString &text)
|
||||
{
|
||||
// (hemirt) pajlada: "we should not be sending whispers through jtv, but
|
||||
|
@ -373,6 +399,22 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
return "";
|
||||
});
|
||||
|
||||
this->registerCommand("/follow", [](const auto &words, auto channel) {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"Twitch has removed the ability to follow users through "
|
||||
"third-party applications. For more information, see "
|
||||
"https://github.com/Chatterino/chatterino2/issues/3076"));
|
||||
return "";
|
||||
});
|
||||
|
||||
this->registerCommand("/unfollow", [](const auto &words, auto channel) {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"Twitch has removed the ability to unfollow users through "
|
||||
"third-party applications. For more information, see "
|
||||
"https://github.com/Chatterino/chatterino2/issues/3076"));
|
||||
return "";
|
||||
});
|
||||
|
||||
/// Supported commands
|
||||
|
||||
this->registerCommand(
|
||||
|
@ -407,90 +449,6 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
|
||||
this->registerCommand("/unblock", unblockLambda);
|
||||
|
||||
this->registerCommand("/follow", [](const auto &words, auto channel) {
|
||||
if (words.size() < 2)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage("Usage: /follow [user]"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to follow someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = words.at(1);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[currentUser, channel, target](const auto &targetUser) {
|
||||
getHelix()->followUser(
|
||||
currentUser->getUserId(), targetUser.id,
|
||||
[channel, target]() {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"You successfully followed " + target));
|
||||
},
|
||||
[channel, target]() {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("User %1 could not be followed, an unknown "
|
||||
"error occurred!")
|
||||
.arg(target)));
|
||||
});
|
||||
},
|
||||
[channel, target] {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("User %1 could not be followed, "
|
||||
"no user with that name found!")
|
||||
.arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
this->registerCommand("/unfollow", [](const auto &words, auto channel) {
|
||||
if (words.size() < 2)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage("Usage: /unfollow [user]"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
if (currentUser->isAnon())
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to unfollow someone!"));
|
||||
return "";
|
||||
}
|
||||
|
||||
auto target = words.at(1);
|
||||
|
||||
getHelix()->getUserByName(
|
||||
target,
|
||||
[currentUser, channel, target](const auto &targetUser) {
|
||||
getHelix()->unfollowUser(
|
||||
currentUser->getUserId(), targetUser.id,
|
||||
[channel, target]() {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"You successfully unfollowed " + target));
|
||||
},
|
||||
[channel, target]() {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"An error occurred while unfollowing " + target));
|
||||
});
|
||||
},
|
||||
[channel, target] {
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("User %1 could not be followed!").arg(target)));
|
||||
});
|
||||
|
||||
return "";
|
||||
});
|
||||
|
||||
this->registerCommand("/user", [](const auto &words, auto channel) {
|
||||
if (words.size() < 2)
|
||||
{
|
||||
|
@ -498,16 +456,17 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
makeSystemMessage("Usage /user [user] (channel)"));
|
||||
return "";
|
||||
}
|
||||
QString userName = words[1];
|
||||
stripUserName(userName);
|
||||
|
||||
QString channelName = channel->getName();
|
||||
|
||||
if (words.size() > 2)
|
||||
{
|
||||
channelName = words[2];
|
||||
if (channelName[0] == '#')
|
||||
{
|
||||
channelName.remove(0, 1);
|
||||
stripChannelName(channelName);
|
||||
}
|
||||
}
|
||||
openTwitchUsercard(channelName, words[1]);
|
||||
openTwitchUsercard(channelName, userName);
|
||||
|
||||
return "";
|
||||
});
|
||||
|
@ -519,10 +478,12 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||
return "";
|
||||
}
|
||||
|
||||
QString userName = words[1];
|
||||
stripUserName(userName);
|
||||
auto *userPopup = new UserInfoPopup(
|
||||
getSettings()->autoCloseUserPopup,
|
||||
static_cast<QWidget *>(&(getApp()->windows->getMainWindow())));
|
||||
userPopup->setData(words[1], channel);
|
||||
userPopup->setData(userName, channel);
|
||||
userPopup->move(QCursor::pos());
|
||||
userPopup->show();
|
||||
return "";
|
||||
|
|
|
@ -60,11 +60,6 @@ public:
|
|||
return this->parser_->valid();
|
||||
}
|
||||
|
||||
bool filter(const MessagePtr &message) const
|
||||
{
|
||||
return this->parser_->execute(message);
|
||||
}
|
||||
|
||||
bool filter(const filterparser::ContextMap &context) const
|
||||
{
|
||||
return this->parser_->execute(context);
|
||||
|
|
|
@ -36,12 +36,13 @@ public:
|
|||
this->listener_.disconnect();
|
||||
}
|
||||
|
||||
bool filter(const MessagePtr &m) const
|
||||
bool filter(const MessagePtr &m, ChannelPtr channel) const
|
||||
{
|
||||
if (this->filters_.size() == 0)
|
||||
return true;
|
||||
|
||||
filterparser::ContextMap context = filterparser::buildContextMap(m);
|
||||
filterparser::ContextMap context =
|
||||
filterparser::buildContextMap(m, channel.get());
|
||||
for (const auto &f : this->filters_.values())
|
||||
{
|
||||
if (!f->valid() || !f->filter(context))
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#include "FilterParser.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "controllers/filters/parser/Types.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
|
||||
namespace filterparser {
|
||||
|
||||
ContextMap buildContextMap(const MessagePtr &m)
|
||||
ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
||||
{
|
||||
auto watchingChannel =
|
||||
chatterino::getApp()->twitch.server->watchingChannel.get();
|
||||
|
@ -61,8 +62,7 @@ ContextMap buildContextMap(const MessagePtr &m)
|
|||
subLength = m->badgeInfos.at(subBadge).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ContextMap vars = {
|
||||
{"author.badges", std::move(badges)},
|
||||
{"author.color", m->usernameColor},
|
||||
{"author.name", m->displayName},
|
||||
|
@ -82,6 +82,19 @@ ContextMap buildContextMap(const MessagePtr &m)
|
|||
{"message.content", m->messageText},
|
||||
{"message.length", m->messageText.length()},
|
||||
};
|
||||
{
|
||||
using namespace chatterino;
|
||||
auto *tc = dynamic_cast<TwitchChannel *>(channel);
|
||||
if (channel && !channel->isEmpty() && tc)
|
||||
{
|
||||
vars["channel.live"] = tc->isLive();
|
||||
}
|
||||
else
|
||||
{
|
||||
vars["channel.live"] = false;
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
FilterParser::FilterParser(const QString &text)
|
||||
|
@ -91,12 +104,6 @@ FilterParser::FilterParser(const QString &text)
|
|||
{
|
||||
}
|
||||
|
||||
bool FilterParser::execute(const MessagePtr &message) const
|
||||
{
|
||||
auto context = buildContextMap(message);
|
||||
return this->execute(context);
|
||||
}
|
||||
|
||||
bool FilterParser::execute(const ContextMap &context) const
|
||||
{
|
||||
return this->builtExpression_->execute(context).toBool();
|
||||
|
|
|
@ -3,15 +3,20 @@
|
|||
#include "controllers/filters/parser/Tokenizer.hpp"
|
||||
#include "controllers/filters/parser/Types.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace filterparser {
|
||||
|
||||
ContextMap buildContextMap(const MessagePtr &m);
|
||||
ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel);
|
||||
|
||||
class FilterParser
|
||||
{
|
||||
public:
|
||||
FilterParser(const QString &text);
|
||||
bool execute(const MessagePtr &message) const;
|
||||
bool execute(const ContextMap &context) const;
|
||||
bool valid() const;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ static const QMap<QString, QString> validIdentifiersMap = {
|
|||
{"author.sub_length", "author sub length"},
|
||||
{"channel.name", "channel name"},
|
||||
{"channel.watching", "/watching channel?"},
|
||||
{"channel.live", "Channel live?"},
|
||||
{"flags.highlighted", "highlighted?"},
|
||||
{"flags.points_redeemed", "redeemed points?"},
|
||||
{"flags.sub_message", "sub/resub message?"},
|
||||
|
|
63
src/controllers/ignores/IgnoreController.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
bool isIgnoredMessage(IgnoredMessageParameters &¶ms)
|
||||
{
|
||||
if (!params.message.isEmpty())
|
||||
{
|
||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||
auto phrases = getCSettings().ignoredMessages.readOnly();
|
||||
for (const auto &phrase : *phrases)
|
||||
{
|
||||
if (phrase.isBlock() && phrase.isMatch(params.message))
|
||||
{
|
||||
qCDebug(chatterinoMessage)
|
||||
<< "Blocking message because it contains ignored phrase"
|
||||
<< phrase.getPattern();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!params.twitchUserID.isEmpty() &&
|
||||
getSettings()->enableTwitchBlockedUsers)
|
||||
{
|
||||
auto sourceUserID = params.twitchUserID;
|
||||
|
||||
auto blocks =
|
||||
getApp()->accounts->twitch.getCurrent()->accessBlockedUserIds();
|
||||
|
||||
if (auto it = blocks->find(sourceUserID); it != blocks->end())
|
||||
{
|
||||
switch (static_cast<ShowIgnoredUsersMessages>(
|
||||
getSettings()->showBlockedUsersMessages.getValue()))
|
||||
{
|
||||
case ShowIgnoredUsersMessages::IfModerator:
|
||||
if (params.isMod || params.isBroadcaster)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ShowIgnoredUsersMessages::IfBroadcaster:
|
||||
if (params.isBroadcaster)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case ShowIgnoredUsersMessages::Never:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,7 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
|
||||
|
||||
struct IgnoredMessageParameters {
|
||||
QString message;
|
||||
|
||||
QString twitchUserID;
|
||||
bool isMod;
|
||||
bool isBroadcaster;
|
||||
};
|
||||
|
||||
bool isIgnoredMessage(IgnoredMessageParameters &¶ms);
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
77
src/controllers/nicknames/Nickname.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/serialize.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Nickname
|
||||
{
|
||||
public:
|
||||
Nickname(const QString &name, const QString &replace)
|
||||
: name_(name)
|
||||
, replace_(replace)
|
||||
{
|
||||
}
|
||||
|
||||
const QString &name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
const QString &replace() const
|
||||
{
|
||||
return this->replace_;
|
||||
}
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
QString replace_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
||||
template <>
|
||||
struct Serialize<chatterino::Nickname> {
|
||||
static rapidjson::Value get(const chatterino::Nickname &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "name", value.name(), a);
|
||||
chatterino::rj::set(ret, "replace", value.replace(), a);
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<chatterino::Nickname> {
|
||||
static chatterino::Nickname get(const rapidjson::Value &value,
|
||||
bool *error = nullptr)
|
||||
{
|
||||
if (!value.IsObject())
|
||||
{
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
return chatterino::Nickname(QString(), QString());
|
||||
}
|
||||
|
||||
QString _name;
|
||||
QString _replace;
|
||||
|
||||
chatterino::rj::getSafe(value, "name", _name);
|
||||
chatterino::rj::getSafe(value, "replace", _replace);
|
||||
|
||||
return chatterino::Nickname(_name, _replace);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pajlada
|
31
src/controllers/nicknames/NicknamesModel.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "NicknamesModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NicknamesModel::NicknamesModel(QObject *parent)
|
||||
: SignalVectorModel<Nickname>(2, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
Nickname NicknamesModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Nickname &original)
|
||||
{
|
||||
return Nickname{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::DisplayRole).toString()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void NicknamesModel::getRowFromItem(const Nickname &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.name());
|
||||
setStringItem(row[1], item.replace());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
25
src/controllers/nicknames/NicknamesModel.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/nicknames/Nickname.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NicknamesModel : public SignalVectorModel<Nickname>
|
||||
{
|
||||
public:
|
||||
explicit NicknamesModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual Nickname getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Nickname &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const Nickname &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Application.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
|
@ -104,20 +105,9 @@ void SharedMessageBuilder::parse()
|
|||
|
||||
bool SharedMessageBuilder::isIgnored() const
|
||||
{
|
||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||
auto phrases = getCSettings().ignoredMessages.readOnly();
|
||||
for (const auto &phrase : *phrases)
|
||||
{
|
||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||
{
|
||||
qCDebug(chatterinoMessage)
|
||||
<< "Blocking message because it contains ignored phrase"
|
||||
<< phrase.getPattern();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isIgnoredMessage({
|
||||
/*.message = */ this->originalMessage_,
|
||||
});
|
||||
}
|
||||
|
||||
void SharedMessageBuilder::parseUsernameColor()
|
||||
|
|
|
@ -15,6 +15,10 @@ const int RECONNECT_BASE_INTERVAL = 2000;
|
|||
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
|
||||
const int MAX_FALLOFF_COUNTER = 60;
|
||||
|
||||
// Ratelimits for joinBucket_
|
||||
const int JOIN_RATELIMIT_BUDGET = 18;
|
||||
const int JOIN_RATELIMIT_COOLDOWN = 10500;
|
||||
|
||||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
|
@ -23,6 +27,17 @@ AbstractIrcServer::AbstractIrcServer()
|
|||
this->writeConnection_->moveToThread(
|
||||
QCoreApplication::instance()->thread());
|
||||
|
||||
// Apply a leaky bucket rate limiting to JOIN messages
|
||||
auto actuallyJoin = [&](QString message) {
|
||||
if (!this->channels.contains(message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->readConnection_->sendRaw("JOIN #" + message);
|
||||
};
|
||||
this->joinBucket_.reset(new RatelimitBucket(
|
||||
JOIN_RATELIMIT_BUDGET, JOIN_RATELIMIT_COOLDOWN, actuallyJoin, this));
|
||||
|
||||
QObject::connect(this->writeConnection_.get(),
|
||||
&Communi::IrcConnection::messageReceived, this,
|
||||
[this](auto msg) {
|
||||
|
@ -214,11 +229,6 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
|||
{
|
||||
this->readConnection_->sendRaw("PART #" + channelName);
|
||||
}
|
||||
|
||||
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
||||
{
|
||||
this->writeConnection_->sendRaw("PART #" + channelName);
|
||||
}
|
||||
}));
|
||||
|
||||
// join irc channel
|
||||
|
@ -229,15 +239,7 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
|||
{
|
||||
if (this->readConnection_->isConnected())
|
||||
{
|
||||
this->readConnection_->sendRaw("JOIN #" + channelName);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
||||
{
|
||||
if (this->readConnection_->isConnected())
|
||||
{
|
||||
this->writeConnection_->sendRaw("JOIN #" + channelName);
|
||||
this->joinBucket_->send(channelName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -297,7 +299,7 @@ void AbstractIrcServer::onReadConnected(IrcConnection *connection)
|
|||
{
|
||||
if (auto channel = weak.lock())
|
||||
{
|
||||
connection->sendRaw("JOIN #" + channel->getName());
|
||||
this->joinBucket_->send(channel->getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "common/Common.hpp"
|
||||
#include "providers/irc/IrcConnection2.hpp"
|
||||
#include "util/RatelimitBucket.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -88,6 +89,10 @@ private:
|
|||
QObjectPtr<IrcConnection> writeConnection_ = nullptr;
|
||||
QObjectPtr<IrcConnection> readConnection_ = nullptr;
|
||||
|
||||
// Our rate limiting bucket for the Twitch join rate limits
|
||||
// https://dev.twitch.tv/docs/irc/guide#rate-limits
|
||||
QObjectPtr<RatelimitBucket> joinBucket_;
|
||||
|
||||
QTimer reconnectTimer_;
|
||||
int falloffCounter_ = 1;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "providers/twitch/PubsubActions.hpp"
|
||||
#include "providers/twitch/PubsubHelpers.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
|
@ -23,7 +24,8 @@ namespace chatterino {
|
|||
|
||||
static const char *pingPayload = "{\"type\":\"PING\"}";
|
||||
|
||||
static std::map<QString, QString> sentMessages;
|
||||
static std::map<QString, RequestMessage> sentListens;
|
||||
static std::map<QString, RequestMessage> sentUnlistens;
|
||||
|
||||
namespace detail {
|
||||
|
||||
|
@ -59,8 +61,9 @@ namespace detail {
|
|||
// This PubSubClient is already at its peak listens
|
||||
return false;
|
||||
}
|
||||
|
||||
this->numListens_ += numRequestedListens;
|
||||
DebugCount::increase("PubSub topic pending listens",
|
||||
numRequestedListens);
|
||||
|
||||
for (const auto &topic : message["data"]["topics"].GetArray())
|
||||
{
|
||||
|
@ -68,12 +71,11 @@ namespace detail {
|
|||
Listener{topic.GetString(), false, false, false});
|
||||
}
|
||||
|
||||
auto uuid = generateUuid();
|
||||
|
||||
rj::set(message, "nonce", uuid);
|
||||
auto nonce = generateUuid();
|
||||
rj::set(message, "nonce", nonce);
|
||||
|
||||
QString payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
sentListens[nonce] = RequestMessage{payload, numRequestedListens};
|
||||
|
||||
this->send(payload.toUtf8());
|
||||
|
||||
|
@ -103,14 +105,19 @@ namespace detail {
|
|||
return;
|
||||
}
|
||||
|
||||
int numRequestedUnlistens = topics.size();
|
||||
|
||||
this->numListens_ -= numRequestedUnlistens;
|
||||
DebugCount::increase("PubSub topic pending unlistens",
|
||||
numRequestedUnlistens);
|
||||
|
||||
auto message = createUnlistenMessage(topics);
|
||||
|
||||
auto uuid = generateUuid();
|
||||
|
||||
rj::set(message, "nonce", generateUuid());
|
||||
auto nonce = generateUuid();
|
||||
rj::set(message, "nonce", nonce);
|
||||
|
||||
QString payload = rj::stringify(message);
|
||||
sentMessages[uuid] = payload;
|
||||
sentUnlistens[nonce] = RequestMessage{payload, numRequestedUnlistens};
|
||||
|
||||
this->send(payload.toUtf8());
|
||||
}
|
||||
|
@ -865,6 +872,13 @@ PubSub::PubSub()
|
|||
|
||||
void PubSub::addClient()
|
||||
{
|
||||
if (this->addingClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->addingClient = true;
|
||||
|
||||
websocketpp::lib::error_code ec;
|
||||
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
|
||||
|
||||
|
@ -998,6 +1012,8 @@ void PubSub::listen(rapidjson::Document &&msg)
|
|||
|
||||
this->requests.emplace_back(
|
||||
std::make_unique<rapidjson::Document>(std::move(msg)));
|
||||
|
||||
DebugCount::increase("PubSub topic backlog");
|
||||
}
|
||||
|
||||
bool PubSub::tryListen(rapidjson::Document &msg)
|
||||
|
@ -1066,7 +1082,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
|
||||
if (type == "RESPONSE")
|
||||
{
|
||||
this->handleListenResponse(msg);
|
||||
this->handleResponse(msg);
|
||||
}
|
||||
else if (type == "MESSAGE")
|
||||
{
|
||||
|
@ -1107,6 +1123,9 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
|
||||
void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
||||
{
|
||||
DebugCount::increase("PubSub connections");
|
||||
this->addingClient = false;
|
||||
|
||||
auto client =
|
||||
std::make_shared<detail::PubSubClient>(this->websocketClient, hdl);
|
||||
|
||||
|
@ -1123,6 +1142,7 @@ void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
|||
const auto &request = *it;
|
||||
if (client->listen(*request))
|
||||
{
|
||||
DebugCount::decrease("PubSub topic backlog");
|
||||
it = this->requests.erase(it);
|
||||
}
|
||||
else
|
||||
|
@ -1130,10 +1150,16 @@ void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
|||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->requests.empty())
|
||||
{
|
||||
this->addClient();
|
||||
}
|
||||
}
|
||||
|
||||
void PubSub::onConnectionClose(WebsocketHandle hdl)
|
||||
{
|
||||
DebugCount::decrease("PubSub connections");
|
||||
auto clientIt = this->clients.find(hdl);
|
||||
|
||||
// If this assert goes off, there's something wrong with the connection
|
||||
|
@ -1169,26 +1195,63 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
|||
return ctx;
|
||||
}
|
||||
|
||||
void PubSub::handleListenResponse(const rapidjson::Document &msg)
|
||||
void PubSub::handleResponse(const rapidjson::Document &msg)
|
||||
{
|
||||
QString error;
|
||||
|
||||
if (rj::getSafe(msg, "error", error))
|
||||
{
|
||||
if (!rj::getSafe(msg, "error", error))
|
||||
return;
|
||||
|
||||
QString nonce;
|
||||
rj::getSafe(msg, "nonce", nonce);
|
||||
|
||||
if (error.isEmpty())
|
||||
const bool failed = !error.isEmpty();
|
||||
|
||||
if (failed)
|
||||
{
|
||||
qCDebug(chatterinoPubsub)
|
||||
<< "Successfully listened to nonce" << nonce;
|
||||
// Nothing went wrong
|
||||
<< QString("Error %1 on nonce %2").arg(error, nonce);
|
||||
}
|
||||
|
||||
if (auto it = sentListens.find(nonce); it != sentListens.end())
|
||||
{
|
||||
this->handleListenResponse(it->second, failed);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto it = sentUnlistens.find(nonce); it != sentUnlistens.end())
|
||||
{
|
||||
this->handleUnlistenResponse(it->second, failed);
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(chatterinoPubsub)
|
||||
<< "PubSub error:" << error << "on nonce" << nonce;
|
||||
return;
|
||||
<< "Response on unused" << nonce << "client/topic listener mismatch?";
|
||||
}
|
||||
|
||||
void PubSub::handleListenResponse(const RequestMessage &msg, bool failed)
|
||||
{
|
||||
DebugCount::decrease("PubSub topic pending listens", msg.topicCount);
|
||||
if (failed)
|
||||
{
|
||||
DebugCount::increase("PubSub topic failed listens", msg.topicCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugCount::increase("PubSub topic listening", msg.topicCount);
|
||||
}
|
||||
}
|
||||
|
||||
void PubSub::handleUnlistenResponse(const RequestMessage &msg, bool failed)
|
||||
{
|
||||
DebugCount::decrease("PubSub topic pending unlistens", msg.topicCount);
|
||||
if (failed)
|
||||
{
|
||||
DebugCount::increase("PubSub topic failed unlistens", msg.topicCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugCount::decrease("PubSub topic listening", msg.topicCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,11 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
|
|||
#define MAX_PUBSUB_LISTENS 50
|
||||
#define MAX_PUBSUB_CONNECTIONS 10
|
||||
|
||||
struct RequestMessage {
|
||||
QString payload;
|
||||
int topicCount;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct Listener {
|
||||
|
@ -172,6 +177,7 @@ private:
|
|||
bool isListeningToTopic(const QString &topic);
|
||||
|
||||
void addClient();
|
||||
std::atomic<bool> addingClient{false};
|
||||
|
||||
State state = State::Connected;
|
||||
|
||||
|
@ -192,7 +198,9 @@ private:
|
|||
void onConnectionClose(websocketpp::connection_hdl hdl);
|
||||
WebsocketContextPtr onTLSInit(websocketpp::connection_hdl hdl);
|
||||
|
||||
void handleListenResponse(const rapidjson::Document &msg);
|
||||
void handleResponse(const rapidjson::Document &msg);
|
||||
void handleListenResponse(const RequestMessage &msg, bool failed);
|
||||
void handleUnlistenResponse(const RequestMessage &msg, bool failed);
|
||||
void handleMessageResponse(const rapidjson::Value &data);
|
||||
|
||||
void runThread();
|
||||
|
|
|
@ -186,23 +186,6 @@ void TwitchAccount::unblockUser(QString userId, std::function<void()> onSuccess,
|
|||
std::move(onFailure));
|
||||
}
|
||||
|
||||
void TwitchAccount::checkFollow(const QString targetUserID,
|
||||
std::function<void(FollowResult)> onFinished)
|
||||
{
|
||||
const auto onResponse = [onFinished](bool following, const auto &record) {
|
||||
if (!following)
|
||||
{
|
||||
onFinished(FollowResult_NotFollowing);
|
||||
return;
|
||||
}
|
||||
|
||||
onFinished(FollowResult_Following);
|
||||
};
|
||||
|
||||
getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse,
|
||||
[] {});
|
||||
}
|
||||
|
||||
SharedAccessGuard<const std::set<TwitchUser>> TwitchAccount::accessBlocks()
|
||||
const
|
||||
{
|
||||
|
@ -215,7 +198,7 @@ SharedAccessGuard<const std::set<QString>> TwitchAccount::accessBlockedUserIds()
|
|||
return this->ignoresUserIds_.accessConst();
|
||||
}
|
||||
|
||||
void TwitchAccount::loadEmotes()
|
||||
void TwitchAccount::loadEmotes(std::weak_ptr<Channel> weakChannel)
|
||||
{
|
||||
qCDebug(chatterinoTwitch)
|
||||
<< "Loading Twitch emotes for user" << this->getUserName();
|
||||
|
@ -237,9 +220,14 @@ void TwitchAccount::loadEmotes()
|
|||
// TODO(zneix): Once Helix adds Get User Emotes we could remove this hacky solution
|
||||
// For now, this is necessary as Kraken's equivalent doesn't return all emotes
|
||||
// See: https://twitch.uservoice.com/forums/310213-developers/suggestions/43599900
|
||||
this->loadUserstateEmotes([=] {
|
||||
this->loadUserstateEmotes([this, weakChannel] {
|
||||
// Fill up emoteData with emote sets that were returned in a Kraken call, but aren't present in emoteData.
|
||||
this->loadKrakenEmotes();
|
||||
if (auto channel = weakChannel.lock(); channel != nullptr)
|
||||
{
|
||||
channel->addMessage(
|
||||
makeSystemMessage("Twitch subscriber emotes reloaded."));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -108,13 +108,10 @@ public:
|
|||
void unblockUser(QString userId, std::function<void()> onSuccess,
|
||||
std::function<void()> onFailure);
|
||||
|
||||
void checkFollow(const QString targetUserID,
|
||||
std::function<void(FollowResult)> onFinished);
|
||||
|
||||
SharedAccessGuard<const std::set<QString>> accessBlockedUserIds() const;
|
||||
SharedAccessGuard<const std::set<TwitchUser>> accessBlocks() const;
|
||||
|
||||
void loadEmotes();
|
||||
void loadEmotes(std::weak_ptr<Channel> weakChannel = {});
|
||||
// loadUserstateEmotes loads emote sets that are part of the USERSTATE emote-sets key
|
||||
// this function makes sure not to load emote sets that have already been loaded
|
||||
void loadUserstateEmotes(std::function<void()> callback);
|
||||
|
|
|
@ -286,7 +286,8 @@ void TwitchChannel::addChannelPointReward(const ChannelPointReward &reward)
|
|||
if (!reward.isUserInputRequired)
|
||||
{
|
||||
MessageBuilder builder;
|
||||
TwitchMessageBuilder::appendChannelPointRewardMessage(reward, &builder);
|
||||
TwitchMessageBuilder::appendChannelPointRewardMessage(
|
||||
reward, &builder, this->isMod(), this->isBroadcaster());
|
||||
this->addMessage(builder.release());
|
||||
return;
|
||||
}
|
||||
|
@ -375,8 +376,18 @@ void TwitchChannel::sendMessage(const QString &message)
|
|||
{
|
||||
if (parsedMessage == this->lastSentMessage_)
|
||||
{
|
||||
auto spaceIndex = parsedMessage.indexOf(' ');
|
||||
if (spaceIndex == -1)
|
||||
{
|
||||
// no spaces found, fall back to old magic character
|
||||
parsedMessage.append(MAGIC_MESSAGE_SUFFIX);
|
||||
}
|
||||
else
|
||||
{
|
||||
// replace the space we found in spaceIndex with two spaces
|
||||
parsedMessage.replace(spaceIndex, 1, " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ EmotePtr TwitchEmotes::getOrCreateEmote(const EmoteId &id,
|
|||
Image::fromUrl(getEmoteLink(id, "3.0"), 0.25),
|
||||
},
|
||||
Tooltip{name.toHtmlEscaped() + "<br>Twitch Emote"},
|
||||
Url{QString("https://twitchemotes.com/emotes/%1").arg(id.string)}});
|
||||
});
|
||||
}
|
||||
|
||||
return shared;
|
||||
|
|
|
@ -197,34 +197,13 @@ void TwitchIrcServer::writeConnectionMessageReceived(
|
|||
// Below commands enabled through the twitch.tv/commands CAP REQ
|
||||
if (command == "USERSTATE")
|
||||
{
|
||||
// Received USERSTATE upon PRIVMSGing
|
||||
// Received USERSTATE upon sending PRIVMSG messages
|
||||
handler.handleUserStateMessage(message);
|
||||
}
|
||||
else if (command == "NOTICE")
|
||||
{
|
||||
static std::unordered_set<std::string> readConnectionOnlyIDs{
|
||||
"host_on",
|
||||
"host_off",
|
||||
"host_target_went_offline",
|
||||
"emote_only_on",
|
||||
"emote_only_off",
|
||||
"slow_on",
|
||||
"slow_off",
|
||||
"subs_on",
|
||||
"subs_off",
|
||||
"r9k_on",
|
||||
"r9k_off",
|
||||
|
||||
// Display for user who times someone out. This implies you're a
|
||||
// moderator, at which point you will be connected to PubSub and receive
|
||||
// a better message from there.
|
||||
"timeout_success",
|
||||
"ban_success",
|
||||
|
||||
// Channel suspended notices
|
||||
"msg_channel_suspended",
|
||||
};
|
||||
|
||||
// List of expected NOTICE messages on write connection
|
||||
// https://git.kotmisia.pl/Mm2PL/docs/src/branch/master/irc_msg_ids.md#command-results
|
||||
handler.handleNoticeMessage(
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
}
|
||||
|
|
|
@ -114,44 +114,12 @@ TwitchMessageBuilder::TwitchMessageBuilder(
|
|||
|
||||
bool TwitchMessageBuilder::isIgnored() const
|
||||
{
|
||||
if (SharedMessageBuilder::isIgnored())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
if (getSettings()->enableTwitchBlockedUsers &&
|
||||
this->tags.contains("user-id"))
|
||||
{
|
||||
auto sourceUserID = this->tags.value("user-id").toString();
|
||||
|
||||
auto blocks =
|
||||
app->accounts->twitch.getCurrent()->accessBlockedUserIds();
|
||||
|
||||
if (auto it = blocks->find(sourceUserID); it != blocks->end())
|
||||
{
|
||||
switch (static_cast<ShowIgnoredUsersMessages>(
|
||||
getSettings()->showBlockedUsersMessages.getValue()))
|
||||
{
|
||||
case ShowIgnoredUsersMessages::IfModerator:
|
||||
if (this->channel->isMod() ||
|
||||
this->channel->isBroadcaster())
|
||||
return false;
|
||||
break;
|
||||
case ShowIgnoredUsersMessages::IfBroadcaster:
|
||||
if (this->channel->isBroadcaster())
|
||||
return false;
|
||||
break;
|
||||
case ShowIgnoredUsersMessages::Never:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return isIgnoredMessage({
|
||||
/*.message = */ this->originalMessage_,
|
||||
/*.twitchUserID = */ this->tags.value("user-id").toString(),
|
||||
/*.isMod = */ this->channel->isMod(),
|
||||
/*.isBroadcaster = */ this->channel->isBroadcaster(),
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::triggerHighlights()
|
||||
|
@ -190,7 +158,9 @@ MessagePtr TwitchMessageBuilder::build()
|
|||
this->args.channelPointRewardId);
|
||||
if (reward)
|
||||
{
|
||||
this->appendChannelPointRewardMessage(reward.get(), this);
|
||||
this->appendChannelPointRewardMessage(
|
||||
reward.get(), this, this->channel->isMod(),
|
||||
this->channel->isBroadcaster());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,6 +660,18 @@ void TwitchMessageBuilder::appendUsername()
|
|||
break;
|
||||
}
|
||||
|
||||
auto nicknames = getCSettings().nicknames.readOnly();
|
||||
auto loginLower = this->message().loginName.toLower();
|
||||
|
||||
for (const auto &nickname : *nicknames)
|
||||
{
|
||||
if (nickname.name().toLower() == loginLower)
|
||||
{
|
||||
usernameText = nickname.replace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->args.isSentWhisper)
|
||||
{
|
||||
// TODO(pajlada): Re-implement
|
||||
|
@ -1249,8 +1231,19 @@ Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string)
|
|||
}
|
||||
|
||||
void TwitchMessageBuilder::appendChannelPointRewardMessage(
|
||||
const ChannelPointReward &reward, MessageBuilder *builder)
|
||||
const ChannelPointReward &reward, MessageBuilder *builder, bool isMod,
|
||||
bool isBroadcaster)
|
||||
{
|
||||
if (isIgnoredMessage({
|
||||
/*.message = */ "",
|
||||
/*.twitchUserID = */ reward.user.id,
|
||||
/*.isMod = */ isMod,
|
||||
/*.isBroadcaster = */ isBroadcaster,
|
||||
}))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder->emplace<TimestampElement>();
|
||||
QString redeemed = "Redeemed";
|
||||
QStringList textList;
|
||||
|
|
|
@ -46,7 +46,8 @@ public:
|
|||
MessagePtr build() override;
|
||||
|
||||
static void appendChannelPointRewardMessage(
|
||||
const ChannelPointReward &reward, MessageBuilder *builder);
|
||||
const ChannelPointReward &reward, MessageBuilder *builder, bool isMod,
|
||||
bool isBroadcaster);
|
||||
|
||||
// Message in the /live chat for channel going live
|
||||
static void liveMessage(const QString &channelName,
|
||||
|
|
|
@ -142,25 +142,6 @@ void Helix::getUserFollowers(
|
|||
std::move(failureCallback));
|
||||
}
|
||||
|
||||
void Helix::getUserFollow(
|
||||
QString userId, QString targetId,
|
||||
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
this->fetchUsersFollows(
|
||||
std::move(userId), std::move(targetId),
|
||||
[successCallback](const auto &response) {
|
||||
if (response.data.empty())
|
||||
{
|
||||
successCallback(false, HelixUsersFollowsRecord());
|
||||
return;
|
||||
}
|
||||
|
||||
successCallback(true, response.data[0]);
|
||||
},
|
||||
std::move(failureCallback));
|
||||
}
|
||||
|
||||
void Helix::fetchStreams(
|
||||
QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||
|
@ -354,50 +335,6 @@ void Helix::getGameById(QString gameId,
|
|||
failureCallback);
|
||||
}
|
||||
|
||||
void Helix::followUser(QString userId, QString targetId,
|
||||
std::function<void()> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
urlQuery.addQueryItem("from_id", userId);
|
||||
urlQuery.addQueryItem("to_id", targetId);
|
||||
|
||||
this->makeRequest("users/follows", urlQuery)
|
||||
.type(NetworkRequestType::Post)
|
||||
.onSuccess([successCallback](auto /*result*/) -> Outcome {
|
||||
successCallback();
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto /*result*/) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::unfollowUser(QString userId, QString targetId,
|
||||
std::function<void()> successCallback,
|
||||
HelixFailureCallback failureCallback)
|
||||
{
|
||||
QUrlQuery urlQuery;
|
||||
|
||||
urlQuery.addQueryItem("from_id", userId);
|
||||
urlQuery.addQueryItem("to_id", targetId);
|
||||
|
||||
this->makeRequest("users/follows", urlQuery)
|
||||
.type(NetworkRequestType::Delete)
|
||||
.onSuccess([successCallback](auto /*result*/) -> Outcome {
|
||||
successCallback();
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback](auto /*result*/) {
|
||||
// TODO: make better xd
|
||||
failureCallback();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::createClip(QString channelId,
|
||||
ResultCallback<HelixClip> successCallback,
|
||||
std::function<void(HelixClipError)> failureCallback,
|
||||
|
@ -775,7 +712,7 @@ void Helix::getEmoteSetData(QString emoteSetId,
|
|||
QJsonObject root = result.parseJson();
|
||||
auto data = root.value("data");
|
||||
|
||||
if (!data.isArray())
|
||||
if (!data.isArray() || data.toArray().isEmpty())
|
||||
{
|
||||
failureCallback();
|
||||
return Failure;
|
||||
|
|
|
@ -341,11 +341,6 @@ public:
|
|||
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
void getUserFollow(
|
||||
QString userId, QString targetId,
|
||||
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#get-streams
|
||||
void fetchStreams(QStringList userIds, QStringList userLogins,
|
||||
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||
|
@ -372,16 +367,6 @@ public:
|
|||
void getGameById(QString gameId, ResultCallback<HelixGame> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#create-user-follows
|
||||
void followUser(QString userId, QString targetId,
|
||||
std::function<void()> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#delete-user-follows
|
||||
void unfollowUser(QString userId, QString targetlId,
|
||||
std::function<void()> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#create-clip
|
||||
void createClip(QString channelId,
|
||||
ResultCallback<HelixClip> successCallback,
|
||||
|
|
|
@ -47,26 +47,6 @@ URL: https://dev.twitch.tv/docs/api/reference#get-streams
|
|||
- `TwitchChannel` to get live status, game, title, and viewer count of a channel
|
||||
- `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for
|
||||
|
||||
### Follow User
|
||||
|
||||
URL: https://dev.twitch.tv/docs/api/reference#create-user-follows
|
||||
Requires `user:edit:follows` scope
|
||||
|
||||
- We implement this in `providers/twitch/api/Helix.cpp followUser`
|
||||
Used in:
|
||||
- `widgets/dialogs/UserInfoPopup.cpp` to follow a user by ticking follow checkbox in usercard
|
||||
- `controllers/commands/CommandController.cpp` in /follow command
|
||||
|
||||
### Unfollow User
|
||||
|
||||
URL: https://dev.twitch.tv/docs/api/reference#delete-user-follows
|
||||
Requires `user:edit:follows` scope
|
||||
|
||||
- We implement this in `providers/twitch/api/Helix.cpp unfollowUser`
|
||||
Used in:
|
||||
- `widgets/dialogs/UserInfoPopup.cpp` to unfollow a user by unticking follow checkbox in usercard
|
||||
- `controllers/commands/CommandController.cpp` in /unfollow command
|
||||
|
||||
### Create Clip
|
||||
|
||||
URL: https://dev.twitch.tv/docs/api/reference#create-clip
|
||||
|
|
|
@ -191,7 +191,6 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
|
|||
const QJsonObject &root)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QString action = root.value("action").toString();
|
||||
|
||||
if (action.isNull())
|
||||
|
@ -211,13 +210,20 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
|
|||
AttachedWindow::GetArgs args;
|
||||
args.winId = root.value("winId").toString();
|
||||
args.yOffset = root.value("yOffset").toInt(-1);
|
||||
args.x = root.value("size").toObject().value("x").toInt(-1);
|
||||
args.width = root.value("size").toObject().value("width").toInt(-1);
|
||||
args.height = root.value("size").toObject().value("height").toInt(-1);
|
||||
|
||||
{
|
||||
const auto sizeObject = root.value("size").toObject();
|
||||
args.x = sizeObject.value("x").toDouble(-1.0);
|
||||
args.pixelRatio = sizeObject.value("pixelRatio").toDouble(-1.0);
|
||||
args.width = sizeObject.value("width").toInt(-1);
|
||||
args.height = sizeObject.value("height").toInt(-1);
|
||||
}
|
||||
|
||||
args.fullscreen = attachFullscreen;
|
||||
|
||||
qCDebug(chatterinoNativeMessage)
|
||||
<< args.x << args.width << args.height << args.winId;
|
||||
<< args.x << args.pixelRatio << args.width << args.height
|
||||
<< args.winId;
|
||||
|
||||
if (_type.isNull() || args.winId.isNull())
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ ConcurrentSettings::ConcurrentSettings()
|
|||
, ignoredMessages(*new SignalVector<IgnorePhrase>())
|
||||
, mutedChannels(*new SignalVector<QString>())
|
||||
, filterRecords(*new SignalVector<FilterRecordPtr>())
|
||||
, nicknames(*new SignalVector<Nickname>())
|
||||
, moderationActions(*new SignalVector<ModerationAction>)
|
||||
{
|
||||
persist(this->highlightedMessages, "/highlighting/highlights");
|
||||
|
@ -32,6 +33,7 @@ ConcurrentSettings::ConcurrentSettings()
|
|||
persist(this->ignoredMessages, "/ignore/phrases");
|
||||
persist(this->mutedChannels, "/pings/muted");
|
||||
persist(this->filterRecords, "/filtering/filters");
|
||||
persist(this->nicknames, "/nicknames");
|
||||
// tagged users?
|
||||
persist(this->moderationActions, "/moderation/actions");
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "controllers/highlights/HighlightBadge.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
||||
#include "controllers/nicknames/Nickname.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
#include "util/StreamerMode.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
|
@ -23,6 +24,7 @@ class HighlightBlacklistUser;
|
|||
class IgnorePhrase;
|
||||
class TaggedUser;
|
||||
class FilterRecord;
|
||||
class Nickname;
|
||||
|
||||
/// Settings which are availlable for reading on all threads.
|
||||
class ConcurrentSettings
|
||||
|
@ -37,6 +39,7 @@ public:
|
|||
SignalVector<IgnorePhrase> &ignoredMessages;
|
||||
SignalVector<QString> &mutedChannels;
|
||||
SignalVector<FilterRecordPtr> &filterRecords;
|
||||
SignalVector<Nickname> &nicknames;
|
||||
//SignalVector<TaggedUser> &taggedUsers;
|
||||
SignalVector<ModerationAction> &moderationActions;
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#define LOOKUP_COLOR_COUNT 360
|
||||
|
||||
#include "singletons/Theme.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#define LOOKUP_COLOR_COUNT 360
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
Theme::Theme()
|
||||
|
@ -80,6 +83,16 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
this->splits.background = getColor(0, sat, 1);
|
||||
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
|
||||
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
|
||||
|
||||
// Copy button
|
||||
if (this->isLightTheme())
|
||||
{
|
||||
this->buttons.copy = getResources().buttons.copyDark;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->buttons.copy = getResources().buttons.copyLight;
|
||||
}
|
||||
}
|
||||
|
||||
void Theme::normalizeColor(QColor &color)
|
||||
|
|
|
@ -48,6 +48,10 @@ public:
|
|||
} input;
|
||||
} splits;
|
||||
|
||||
struct {
|
||||
QPixmap copy;
|
||||
} buttons;
|
||||
|
||||
void normalizeColor(QColor &color);
|
||||
|
||||
private:
|
||||
|
|
|
@ -27,6 +27,20 @@ public:
|
|||
reinterpret_cast<int64_t &>(it.value())++;
|
||||
}
|
||||
}
|
||||
static void increase(const QString &name, const int64_t &amount)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value()) += amount;
|
||||
}
|
||||
}
|
||||
|
||||
static void decrease(const QString &name)
|
||||
{
|
||||
|
@ -42,6 +56,20 @@ public:
|
|||
reinterpret_cast<int64_t &>(it.value())--;
|
||||
}
|
||||
}
|
||||
static void decrease(const QString &name, const int64_t &amount)
|
||||
{
|
||||
auto counts = counts_.access();
|
||||
|
||||
auto it = counts->find(name);
|
||||
if (it == counts->end())
|
||||
{
|
||||
counts->insert(name, -amount);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<int64_t &>(it.value()) -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
static QString getDebugText()
|
||||
{
|
||||
|
|
45
src/util/RatelimitBucket.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "RatelimitBucket.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
RatelimitBucket::RatelimitBucket(int budget, int cooldown,
|
||||
std::function<void(QString)> callback,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, budget_(budget)
|
||||
, cooldown_(cooldown)
|
||||
, callback_(callback)
|
||||
{
|
||||
}
|
||||
|
||||
void RatelimitBucket::send(QString channel)
|
||||
{
|
||||
this->queue_.append(channel);
|
||||
|
||||
if (this->budget_ > 0)
|
||||
{
|
||||
this->handleOne();
|
||||
}
|
||||
}
|
||||
|
||||
void RatelimitBucket::handleOne()
|
||||
{
|
||||
if (queue_.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = queue_.takeFirst();
|
||||
|
||||
this->budget_--;
|
||||
callback_(item);
|
||||
|
||||
QTimer::singleShot(cooldown_, this, [this] {
|
||||
this->budget_++;
|
||||
this->handleOne();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
40
src/util/RatelimitBucket.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class RatelimitBucket : public QObject
|
||||
{
|
||||
public:
|
||||
RatelimitBucket(int budget, int cooldown,
|
||||
std::function<void(QString)> callback, QObject *parent);
|
||||
|
||||
void send(QString channel);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief budget_ denotes the amount of calls that can be handled before we need to wait for the cooldown
|
||||
**/
|
||||
int budget_;
|
||||
|
||||
/**
|
||||
* @brief This is the amount of time in milliseconds it takes for one used up budget to be put back into the bucket for use elsewhere
|
||||
**/
|
||||
const int cooldown_;
|
||||
|
||||
std::function<void(QString)> callback_;
|
||||
QList<QString> queue_;
|
||||
|
||||
/**
|
||||
* @brief Run the callback on one entry in the queue.
|
||||
*
|
||||
* This will start a timer that runs after cooldown_ milliseconds that
|
||||
* gives back one "token" to the bucket and calls handleOne again.
|
||||
**/
|
||||
void handleOne();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -93,6 +93,7 @@ AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
|
|||
window->fullscreen_ = args.fullscreen;
|
||||
|
||||
window->x_ = args.x;
|
||||
window->pixelRatio_ = args.pixelRatio;
|
||||
|
||||
if (args.height != -1)
|
||||
{
|
||||
|
@ -276,7 +277,16 @@ void AttachedWindow::updateWindowRect(void *_attachedPtr)
|
|||
// offset
|
||||
int o = this->fullscreen_ ? 0 : 8;
|
||||
|
||||
if (this->x_ != -1)
|
||||
if (this->pixelRatio_ != -1.0)
|
||||
{
|
||||
::MoveWindow(
|
||||
hwnd,
|
||||
int(rect.left + this->x_ * scale * this->pixelRatio_ + o - 2),
|
||||
int(rect.bottom - this->height_ * scale - o),
|
||||
int(this->width_ * scale), int(this->height_ * scale), true);
|
||||
}
|
||||
//support for old extension version 1.3
|
||||
else if (this->x_ != -1.0)
|
||||
{
|
||||
::MoveWindow(hwnd, int(rect.left + this->x_ * scale + o),
|
||||
int(rect.bottom - this->height_ * scale - o),
|
||||
|
|
|
@ -17,7 +17,8 @@ public:
|
|||
struct GetArgs {
|
||||
QString winId;
|
||||
int yOffset = -1;
|
||||
int x = -1;
|
||||
double x = -1;
|
||||
double pixelRatio = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
bool fullscreen = false;
|
||||
|
@ -54,7 +55,8 @@ private:
|
|||
void *target_;
|
||||
int yOffset_;
|
||||
int currentYOffset_;
|
||||
int x_ = -1;
|
||||
double x_ = -1;
|
||||
double pixelRatio_ = -1;
|
||||
int width_ = 360;
|
||||
int height_ = -1;
|
||||
bool fullscreen_ = false;
|
||||
|
|
|
@ -641,11 +641,7 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message,
|
|||
long *result)
|
||||
{
|
||||
#ifdef USEWINSDK
|
||||
# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
|
||||
MSG *msg = *reinterpret_cast<MSG **>(message);
|
||||
# else
|
||||
MSG *msg = reinterpret_cast<MSG *>(message);
|
||||
# endif
|
||||
|
||||
bool returnValue = false;
|
||||
|
||||
|
|
|
@ -30,11 +30,7 @@ FramelessEmbedWindow::FramelessEmbedWindow()
|
|||
bool FramelessEmbedWindow::nativeEvent(const QByteArray &eventType,
|
||||
void *message, long *result)
|
||||
{
|
||||
# if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
|
||||
MSG *msg = *reinterpret_cast<MSG **>(message);
|
||||
# else
|
||||
MSG *msg = reinterpret_cast<MSG *>(message);
|
||||
# endif
|
||||
|
||||
if (msg->message == WM_COPYDATA)
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "widgets/settingspages/IgnoresPage.hpp"
|
||||
#include "widgets/settingspages/KeyboardSettingsPage.hpp"
|
||||
#include "widgets/settingspages/ModerationPage.hpp"
|
||||
#include "widgets/settingspages/NicknamesPage.hpp"
|
||||
#include "widgets/settingspages/NotificationPage.hpp"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
|
@ -164,6 +165,7 @@ void SettingsDialog::addTabs()
|
|||
this->addTab([]{return new GeneralPage;}, "General", ":/settings/about.svg");
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
this->addTab([]{return new AccountsPage;}, "Accounts", ":/settings/accounts.svg", SettingsTabId::Accounts);
|
||||
this->addTab([]{return new NicknamesPage;}, "Nicknames", ":/settings/accounts.svg");
|
||||
this->ui_.tabContainer->addSpacing(16);
|
||||
this->addTab([]{return new CommandPage;}, "Commands", ":/settings/commands.svg");
|
||||
this->addTab([]{return new HighlightingPage;}, "Highlights", ":/settings/notifications.svg");
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "providers/twitch/api/Kraken.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/Clipboard.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
|
@ -42,7 +43,7 @@ namespace {
|
|||
{
|
||||
auto label = box.emplace<Label>();
|
||||
auto button = box.emplace<Button>();
|
||||
button->setPixmap(getResources().buttons.copyDark);
|
||||
button->setPixmap(getApp()->themes->buttons.copy);
|
||||
button->setScaleIndependantSize(18, 18);
|
||||
button->setDim(Button::Dim::Lots);
|
||||
QObject::connect(
|
||||
|
@ -232,7 +233,6 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent)
|
|||
{
|
||||
user->addStretch(1);
|
||||
|
||||
user.emplace<QCheckBox>("Follow").assign(&this->ui_.follow);
|
||||
user.emplace<QCheckBox>("Block").assign(&this->ui_.block);
|
||||
user.emplace<QCheckBox>("Ignore highlights")
|
||||
.assign(&this->ui_.ignoreHighlights);
|
||||
|
@ -402,56 +402,6 @@ void UserInfoPopup::scaleChangedEvent(float /*scale*/)
|
|||
|
||||
void UserInfoPopup::installEvents()
|
||||
{
|
||||
std::weak_ptr<bool> hack = this->hack_;
|
||||
|
||||
// follow
|
||||
QObject::connect(
|
||||
this->ui_.follow, &QCheckBox::stateChanged,
|
||||
[this](int newState) mutable {
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
const auto reenableFollowCheckbox = [this] {
|
||||
this->ui_.follow->setEnabled(true);
|
||||
};
|
||||
|
||||
if (!this->ui_.follow->isEnabled())
|
||||
{
|
||||
// We received a state update while the checkbox was disabled
|
||||
// This can only happen from the "check current follow state" call
|
||||
// The state has been updated to properly reflect the users current follow state
|
||||
reenableFollowCheckbox();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (newState)
|
||||
{
|
||||
case Qt::CheckState::Unchecked: {
|
||||
this->ui_.follow->setEnabled(false);
|
||||
getHelix()->unfollowUser(currentUser->getUserId(),
|
||||
this->userId_,
|
||||
reenableFollowCheckbox, [] {
|
||||
//
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::CheckState::PartiallyChecked: {
|
||||
// We deliberately ignore this state
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::CheckState::Checked: {
|
||||
this->ui_.follow->setEnabled(false);
|
||||
getHelix()->followUser(currentUser->getUserId(),
|
||||
this->userId_,
|
||||
reenableFollowCheckbox, [] {
|
||||
//
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
std::shared_ptr<bool> ignoreNext = std::make_shared<bool>(false);
|
||||
|
||||
// block
|
||||
|
@ -615,8 +565,6 @@ void UserInfoPopup::updateLatestMessages()
|
|||
|
||||
void UserInfoPopup::updateUserData()
|
||||
{
|
||||
this->ui_.follow->setEnabled(false);
|
||||
|
||||
std::weak_ptr<bool> hack = this->hack_;
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
|
@ -682,19 +630,6 @@ void UserInfoPopup::updateUserData()
|
|||
// on failure
|
||||
});
|
||||
|
||||
// get follow state
|
||||
currentUser->checkFollow(user.id, [this, hack](auto result) {
|
||||
if (!hack.lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (result != FollowResult_Failed)
|
||||
{
|
||||
this->ui_.follow->setChecked(result == FollowResult_Following);
|
||||
this->ui_.follow->setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
// get ignore state
|
||||
bool isIgnoring = false;
|
||||
|
||||
|
@ -771,7 +706,6 @@ void UserInfoPopup::updateUserData()
|
|||
getHelix()->getUserByName(this->userName_, onUserFetched,
|
||||
onUserFetchFailed);
|
||||
|
||||
this->ui_.follow->setEnabled(false);
|
||||
this->ui_.block->setEnabled(false);
|
||||
this->ui_.ignoreHighlights->setEnabled(false);
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ private:
|
|||
Label *followageLabel = nullptr;
|
||||
Label *subageLabel = nullptr;
|
||||
|
||||
QCheckBox *follow = nullptr;
|
||||
QCheckBox *block = nullptr;
|
||||
QCheckBox *ignoreHighlights = nullptr;
|
||||
|
||||
|
|
|
@ -108,11 +108,7 @@ namespace {
|
|||
});
|
||||
};
|
||||
|
||||
if (creatorFlags.has(MessageElementFlag::TwitchEmote))
|
||||
{
|
||||
addPageLink("TwitchEmotes");
|
||||
}
|
||||
else if (creatorFlags.has(MessageElementFlag::BttvEmote))
|
||||
if (creatorFlags.has(MessageElementFlag::BttvEmote))
|
||||
{
|
||||
addPageLink("BTTV");
|
||||
}
|
||||
|
@ -754,7 +750,7 @@ bool ChannelView::shouldIncludeMessage(const MessagePtr &m) const
|
|||
m->loginName, Qt::CaseInsensitive) == 0)
|
||||
return true;
|
||||
|
||||
return this->channelFilters_->filter(m);
|
||||
return this->channelFilters_->filter(m, this->channel_);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -44,7 +44,7 @@ ChannelPtr SearchPopup::filter(const QString &text, const QString &channelName,
|
|||
}
|
||||
|
||||
if (accept && filterSet)
|
||||
accept = filterSet->filter(message);
|
||||
accept = filterSet->filter(message, channel);
|
||||
|
||||
// If all predicates match, add the message to the channel
|
||||
if (accept)
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
|
||||
#define PIXMAP_WIDTH 500
|
||||
|
||||
#define LINK_CHATTERINO_WIKI "https://wiki.chatterino.com"
|
||||
#define LINK_DONATE "https://streamelements.com/fourtf/tip"
|
||||
#define LINK_CHATTERINO_FEATURES "https://chatterino.com/#features"
|
||||
#define LINK_CHATTERINO_DISCORD "https://discord.gg/7Y5AYhAK4z"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
AboutPage::AboutPage()
|
||||
|
@ -76,6 +81,7 @@ AboutPage::AboutPage()
|
|||
// }
|
||||
}*/
|
||||
|
||||
// Version
|
||||
auto versionInfo = layout.emplace<QGroupBox>("Version");
|
||||
{
|
||||
auto version = Version::instance();
|
||||
|
@ -96,6 +102,20 @@ AboutPage::AboutPage()
|
|||
Qt::LinksAccessibleByMouse);
|
||||
}
|
||||
|
||||
// About Chatterino
|
||||
auto aboutChatterino = layout.emplace<QGroupBox>("About Chatterino...");
|
||||
{
|
||||
auto l = aboutChatterino.emplace<QVBoxLayout>();
|
||||
|
||||
// clang-format off
|
||||
l.emplace<QLabel>("Chatterino Wiki can be found <a href=\"" LINK_CHATTERINO_WIKI "\">here</a>")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Support <a href=\"" LINK_DONATE "\">Chatterino</a>")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("All about Chatterino's <a href=\"" LINK_CHATTERINO_FEATURES "\">features</a>")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Join the official Chatterino <a href=\"" LINK_CHATTERINO_DISCORD "\">Discord</a>")->setOpenExternalLinks(true);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Licenses
|
||||
auto licenses =
|
||||
layout.emplace<QGroupBox>("Open source software used...");
|
||||
{
|
||||
|
@ -129,6 +149,7 @@ AboutPage::AboutPage()
|
|||
":/licenses/lrucache.txt");
|
||||
}
|
||||
|
||||
// Attributions
|
||||
auto attributions = layout.emplace<QGroupBox>("Attributions...");
|
||||
{
|
||||
auto l = attributions.emplace<QVBoxLayout>();
|
||||
|
@ -140,7 +161,6 @@ AboutPage::AboutPage()
|
|||
l.emplace<QLabel>("Google emojis provided by <a href=\"https://google.com\">Google</a>")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Emoji datasource provided by <a href=\"https://www.iamcal.com/\">Cal Henderson</a>"
|
||||
"(<a href=\"https://github.com/iamcal/emoji-data/blob/master/LICENSE\">show license</a>)")->setOpenExternalLinks(true);
|
||||
l.emplace<QLabel>("Twitch emote data provided by <a href=\"https://twitchemotes.com/\">twitchemotes.com</a> through the <a href=\"https://github.com/Chatterino/api\">Chatterino API</a>")->setOpenExternalLinks(true);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
|
|
@ -195,7 +195,7 @@ HighlightingPage::HighlightingPage()
|
|||
}
|
||||
getSettings()->highlightedBadges.append(HighlightBadge{
|
||||
s->badgeName(), s->displayName(), false, false, "",
|
||||
ColorProvider::instance().color(
|
||||
*ColorProvider::instance().color(
|
||||
ColorType::SelfHighlight)});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -160,7 +160,7 @@ ModerationPage::ModerationPage()
|
|||
"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\", \"/w someusername !report {user} was bad in channel {channel}\" or any other custom text commands.<br>"
|
||||
"For deleting messages use /delete {msg-id}.<br><br>"
|
||||
"More information can be found <a href='http://wiki.chatterino.com/Moderation/#moderation-mode'>here</a>.");
|
||||
"More information can be found <a href='https://wiki.chatterino.com/Moderation/#moderation-mode'>here</a>.");
|
||||
label->setOpenExternalLinks(true);
|
||||
label->setWordWrap(true);
|
||||
label->setStyleSheet("color: #bbb");
|
||||
|
|
48
src/widgets/settingspages/NicknamesPage.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "NicknamesPage.hpp"
|
||||
|
||||
#include "controllers/nicknames/NicknamesModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Window.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
|
||||
#include <QTableView>
|
||||
|
||||
#include <QHeaderView>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NicknamesPage::NicknamesPage()
|
||||
{
|
||||
LayoutCreator<NicknamesPage> layoutCreator(this);
|
||||
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
|
||||
|
||||
layout.emplace<QLabel>(
|
||||
"Nicknames do not work with features such as search or user highlights."
|
||||
"\nWith those features you will still need to use the user's original "
|
||||
"name.");
|
||||
EditableModelView *view =
|
||||
layout
|
||||
.emplace<EditableModelView>(
|
||||
(new NicknamesModel(nullptr))
|
||||
->initialized(&getSettings()->nicknames))
|
||||
.getElement();
|
||||
|
||||
view->setTitles({"Username", "Nickname"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Interactive);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
1, QHeaderView::Stretch);
|
||||
|
||||
view->addButtonPressed.connect([] {
|
||||
getSettings()->nicknames.append(Nickname{"Username", "Nickname"});
|
||||
});
|
||||
|
||||
QTimer::singleShot(1, [view] {
|
||||
view->getTableView()->resizeColumnsToContents();
|
||||
view->getTableView()->setColumnWidth(0, 250);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
18
src/widgets/settingspages/NicknamesPage.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
#include <QStringListModel>
|
||||
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NicknamesPage : public SettingsPage
|
||||
{
|
||||
public:
|
||||
NicknamesPage();
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -624,8 +624,10 @@ void Split::popup()
|
|||
window.getNotebook().getOrAddSelectedPage()));
|
||||
|
||||
split->setChannel(this->getIndirectChannel());
|
||||
window.getNotebook().getOrAddSelectedPage()->appendSplit(split);
|
||||
split->setModerationMode(this->getModerationMode());
|
||||
split->setFilters(this->getFilters());
|
||||
|
||||
window.getNotebook().getOrAddSelectedPage()->appendSplit(split);
|
||||
window.show();
|
||||
}
|
||||
|
||||
|
@ -868,8 +870,8 @@ void Split::showSearch()
|
|||
|
||||
void Split::reloadChannelAndSubscriberEmotes()
|
||||
{
|
||||
getApp()->accounts->twitch.getCurrent()->loadEmotes();
|
||||
auto channel = this->getChannel();
|
||||
getApp()->accounts->twitch.getCurrent()->loadEmotes(channel);
|
||||
|
||||
if (auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
{
|
||||
|
|
|
@ -916,10 +916,6 @@ void SplitHeader::themeChangedEvent()
|
|||
}
|
||||
}
|
||||
|
||||
void SplitHeader::moveSplit()
|
||||
{
|
||||
}
|
||||
|
||||
void SplitHeader::reloadChannelEmotes()
|
||||
{
|
||||
auto channel = this->split_->getChannel();
|
||||
|
@ -933,7 +929,8 @@ void SplitHeader::reloadChannelEmotes()
|
|||
|
||||
void SplitHeader::reloadSubscriberEmotes()
|
||||
{
|
||||
getApp()->accounts->twitch.getCurrent()->loadEmotes();
|
||||
auto channel = this->split_->getChannel();
|
||||
getApp()->accounts->twitch.getCurrent()->loadEmotes(channel);
|
||||
}
|
||||
|
||||
void SplitHeader::reconnect()
|
||||
|
|
|
@ -85,7 +85,6 @@ private:
|
|||
std::vector<pajlada::Signals::ScopedConnection> channelConnections_;
|
||||
|
||||
public slots:
|
||||
void moveSplit();
|
||||
void reloadChannelEmotes();
|
||||
void reloadSubscriberEmotes();
|
||||
void reconnect();
|
||||
|
|
|
@ -134,14 +134,18 @@ void SplitInput::themeChangedEvent()
|
|||
QPalette palette, placeholderPalette;
|
||||
|
||||
palette.setColor(QPalette::WindowText, this->theme->splits.input.text);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
placeholderPalette.setColor(
|
||||
QPalette::PlaceholderText,
|
||||
this->theme->messages.textColors.chatPlaceholder);
|
||||
#endif
|
||||
|
||||
this->updateEmoteButton();
|
||||
this->ui_.textEditLength->setPalette(palette);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
this->ui_.textEdit->setPalette(placeholderPalette);
|
||||
#endif
|
||||
this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet);
|
||||
|
||||
this->ui_.hbox->setMargin(
|
||||
|
|
|
@ -11,6 +11,8 @@ set(test_SOURCES
|
|||
${CMAKE_CURRENT_LIST_DIR}/src/ExponentialBackoff.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/TwitchAccount.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/RatelimitBucket.cpp
|
||||
# Add your new file above this line!
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${test_SOURCES})
|
||||
|
|
46
tests/src/RatelimitBucket.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "util/RatelimitBucket.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
TEST(RatelimitBucket, BatchTwoParts)
|
||||
{
|
||||
const int cooldown = 100;
|
||||
int n = 0;
|
||||
auto cb = [&n](QString msg) {
|
||||
qDebug() << msg;
|
||||
++n;
|
||||
};
|
||||
auto bucket = std::make_unique<RatelimitBucket>(5, cooldown, cb, nullptr);
|
||||
bucket->send("1");
|
||||
EXPECT_EQ(n, 1);
|
||||
|
||||
bucket->send("2");
|
||||
EXPECT_EQ(n, 2);
|
||||
|
||||
bucket->send("3");
|
||||
EXPECT_EQ(n, 3);
|
||||
|
||||
bucket->send("4");
|
||||
EXPECT_EQ(n, 4);
|
||||
|
||||
bucket->send("5");
|
||||
EXPECT_EQ(n, 5);
|
||||
|
||||
bucket->send("6");
|
||||
// Rate limit reached, n will not have changed yet. If we wait for the cooldown to run, n should have changed
|
||||
EXPECT_EQ(n, 5);
|
||||
|
||||
QCoreApplication::processEvents();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{cooldown});
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
EXPECT_EQ(n, 6);
|
||||
}
|