mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Compare commits
46 commits
4335048ee6
...
f0c5a781d8
Author | SHA1 | Date | |
---|---|---|---|
f0c5a781d8 | |||
70b2b9a1c3 | |||
2582e34734 | |||
11838c8e16 | |||
7d5967c248 | |||
9eeea8f203 | |||
718696db53 | |||
7f935665f9 | |||
47a14c9041 | |||
93e2bc18fa | |||
ad69755bbb | |||
547ff372e1 | |||
4681fb1117 | |||
451e5f0bf9 | |||
5b6675abb4 | |||
292f9b9734 | |||
13ff11ea75 | |||
c4c62f2796 | |||
1554d7b6a4 | |||
06f950a55b | |||
5c9747e08f | |||
fa5648fd9a | |||
f42ae07408 | |||
78a7ebb9f9 | |||
693d4f401d | |||
416806bb0a | |||
e48d868e8c | |||
1192393039 | |||
99b537ffd9 | |||
77eb9cc1e9 | |||
4a0ef08a00 | |||
a32b962c5d | |||
65b1ed312c | |||
036a5f3f21 | |||
69a54d944d | |||
9a2c27d258 | |||
60d79ef57e | |||
d085ab578f | |||
c65ebd26bd | |||
d84779f127 | |||
04a46f60dc | |||
d0d240136e | |||
9612eac966 | |||
eb12cfa50b | |||
1006bf955a | |||
485fc5cdb4 |
14
.clang-tidy
14
.clang-tidy
|
@ -45,11 +45,9 @@ CheckOptions:
|
|||
- key: readability-identifier-naming.MemberCase
|
||||
value: camelBack
|
||||
- key: readability-identifier-naming.PrivateMemberIgnoredRegexp
|
||||
value: .*
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: _
|
||||
- key: readability-identifier-naming.ProtectedMemberSuffix
|
||||
value: _
|
||||
value: ^.*_$
|
||||
- key: readability-identifier-naming.ProtectedMemberIgnoredRegexp
|
||||
value: ^.*_$
|
||||
- key: readability-identifier-naming.UnionCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
|
@ -65,5 +63,11 @@ CheckOptions:
|
|||
- key: readability-identifier-naming.LocalPointerIgnoredRegexp
|
||||
value: ^L$
|
||||
|
||||
# Benchmarks
|
||||
- key: readability-identifier-naming.FunctionIgnoredRegexp
|
||||
value: ^BM_[^_]+$
|
||||
- key: readability-identifier-naming.ClassIgnoredRegexp
|
||||
value: ^BM_[^_]+$
|
||||
|
||||
- key: misc-const-correctness.AnalyzeValues
|
||||
value: false
|
||||
|
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -151,7 +151,7 @@ jobs:
|
|||
# WINDOWS
|
||||
- name: Enable Developer Command Prompt (Windows)
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: ilammy/msvc-dev-cmd@v1.12.1
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
|
||||
- name: Setup conan variables (Windows)
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
|
@ -174,7 +174,7 @@ jobs:
|
|||
|
||||
- name: Cache conan packages (Windows)
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ runner.os }}-conan-user-${{ hashFiles('**/conanfile.py') }}${{ env.C2_CONAN_CACHE_SUFFIX }}
|
||||
path: ~/.conan2/
|
||||
|
|
4
.github/workflows/clang-tidy.yml
vendored
4
.github/workflows/clang-tidy.yml
vendored
|
@ -119,7 +119,7 @@ jobs:
|
|||
|
||||
- name: clang-tidy review
|
||||
timeout-minutes: 20
|
||||
uses: ZedThree/clang-tidy-review@v0.14.0
|
||||
uses: ZedThree/clang-tidy-review@v0.16.0
|
||||
with:
|
||||
build_dir: build-clang-tidy
|
||||
config_file: ".clang-tidy"
|
||||
|
@ -145,4 +145,4 @@ jobs:
|
|||
libbenchmark-dev
|
||||
|
||||
- name: clang-tidy-review upload
|
||||
uses: ZedThree/clang-tidy-review/upload@v0.14.0
|
||||
uses: ZedThree/clang-tidy-review/upload@v0.16.0
|
||||
|
|
2
.github/workflows/create-installer.yml
vendored
2
.github/workflows/create-installer.yml
vendored
|
@ -43,7 +43,7 @@ jobs:
|
|||
run: echo "C:\Program Files (x86)\Inno Setup 6\" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.12.1
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
|
||||
- name: Build installer
|
||||
id: build-installer
|
||||
|
|
2
.github/workflows/post-clang-tidy-review.yml
vendored
2
.github/workflows/post-clang-tidy-review.yml
vendored
|
@ -14,6 +14,6 @@ jobs:
|
|||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- uses: ZedThree/clang-tidy-review/post@v0.14.0
|
||||
- uses: ZedThree/clang-tidy-review/post@v0.16.0
|
||||
with:
|
||||
lgtm_comment_body: ""
|
||||
|
|
4
.github/workflows/test-windows.yml
vendored
4
.github/workflows/test-windows.yml
vendored
|
@ -63,7 +63,7 @@ jobs:
|
|||
version: ${{ matrix.qt-version }}
|
||||
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.12.1
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
|
||||
- name: Setup conan variables
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
|
@ -83,7 +83,7 @@ jobs:
|
|||
sccache-test-${{ matrix.os }}-${{ matrix.qt-version }}
|
||||
|
||||
- name: Cache conan packages
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ runner.os }}-conan-user-${{ hashFiles('**/conanfile.py') }}${{ env.C2_CONAN_CACHE_SUFFIX }}
|
||||
path: ~/.conan2/
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# JSON resources should not be prettified...
|
||||
resources/*.json
|
||||
benchmarks/resources/*.json
|
||||
tests/resources/*.json
|
||||
# ...themes should be prettified for readability.
|
||||
!resources/themes/*.json
|
||||
|
||||
|
@ -7,6 +9,7 @@ resources/*.json
|
|||
lib/*/
|
||||
conan-pkgs/*/
|
||||
cmake/sanitizers-cmake/
|
||||
tools/crash-handler
|
||||
|
||||
# Build folders
|
||||
*build-*/
|
||||
|
|
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -3,7 +3,8 @@
|
|||
## Unversioned
|
||||
|
||||
- Major: Allow use of Twitch follower emotes in other channels if subscribed. (#4922)
|
||||
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986)
|
||||
- Major: Add `/automod` split to track automod caught messages across all open channels the user moderates. (#4986, #5026)
|
||||
- Major: Show restricted chat messages and suspicious treatment updates. (#5056, #5060)
|
||||
- Minor: Migrate to the new Get Channel Followers Helix endpoint, fixing follower count not showing up in usercards. (#4809)
|
||||
- Minor: The account switcher is now styled to match your theme. (#4817)
|
||||
- Minor: Add an invisible resize handle to the bottom of frameless user info popups and reply thread popups. (#4795)
|
||||
|
@ -16,8 +17,13 @@
|
|||
- Minor: Add an option to use new experimental smarter emote completion. (#4987)
|
||||
- Minor: Add `--safe-mode` command line option that can be used for troubleshooting when Chatterino is misbehaving or is misconfigured. It disables hiding the settings button & prevents plugins from loading. (#4985)
|
||||
- Minor: Updated the flatpakref link included with nightly builds to point to up-to-date flathub-beta builds. (#5008)
|
||||
- Minor: Add a new completion API for experimental plugins feature. (#5000)
|
||||
- Minor: Add a new completion API for experimental plugins feature. (#5000, #5047)
|
||||
- Minor: Re-enabled _Restart on crash_ option on Windows. (#5012)
|
||||
- Minor: The whisper highlight color can now be configured through the settings. (#5053)
|
||||
- Minor: Added missing periods at various moderator messages and commands. (#5061)
|
||||
- Minor: Improved color selection and display. (#5057)
|
||||
- Minor: Improved Streamlink documentation in the settings dialog. (#5076)
|
||||
- Minor: Normalized the input padding between light & dark themes. (#5095)
|
||||
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
|
||||
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
|
||||
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)
|
||||
|
@ -52,11 +58,16 @@
|
|||
- Bugfix: Hide the Usercard button in the User Info Popup when in special channels. (#4972)
|
||||
- Bugfix: Fixed support for Windows 11 Snap layouts. (#4994)
|
||||
- Bugfix: Fixed some windows appearing between screens. (#4797)
|
||||
- Bugfix: Fixed a crash that could occur when using certain features in a Usercard after closing the split from which it was created. (#5034)
|
||||
- Bugfix: Fixed a crash that could occur when using certain features in a Reply popup after closing the split from which it was created. (#5036)
|
||||
- Bugfix: Fixed a bug on Wayland where tooltips would spawn as separate windows instead of behaving like tooltips. (#4998)
|
||||
- Bugfix: Fixed a crash that could occur when using certain features in a Usercard after closing the split from which it was created. (#5034, #5051)
|
||||
- Bugfix: Fixed a crash that could occur when using certain features in a Reply popup after closing the split from which it was created. (#5036, #5051)
|
||||
- Bugfix: Fixed a bug on Wayland where tooltips would spawn as separate windows instead of behaving like tooltips. (#4998, #5040)
|
||||
- Bugfix: Fixes to section deletion in text input fields. (#5013)
|
||||
- Bugfix: Show user text input within watch streak notices. (#5029)
|
||||
- Bugfix: Fixed avatar in usercard and moderation button triggering when releasing the mouse outside their area. (#5052)
|
||||
- Bugfix: Fixed moderator-only topics being subscribed to for non-moderators. (#5056)
|
||||
- Bugfix: Fixed a bug where buttons would remain in a hovered state after leaving them. (#5077)
|
||||
- Bugfix: Fixed popup windows not persisting between restarts. (#5081)
|
||||
- Bugfix: Fixed splits not retaining their focus after minimizing. (#5080)
|
||||
- Dev: Run miniaudio in a separate thread, and simplify it to not manage the device ourselves. There's a chance the simplification is a bad idea. (#4978)
|
||||
- Dev: Change clang-format from v14 to v16. (#4929)
|
||||
- Dev: Fixed UTF16 encoding of `modes` file for the installer. (#4791)
|
||||
|
@ -83,6 +94,8 @@
|
|||
- Dev: Refactor `Emoji`'s EmojiMap into a vector. (#4980)
|
||||
- Dev: Refactor `DebugCount` and add copy button to debug popup. (#4921)
|
||||
- Dev: Refactor `common/Credentials`. (#4979)
|
||||
- Dev: Refactor chat logger. (#5058)
|
||||
- Dev: Refactor Twitch PubSub client. (#5059)
|
||||
- Dev: Changed lifetime of context menus. (#4924)
|
||||
- Dev: Renamed `tools` directory to `scripts`. (#5035)
|
||||
- Dev: Refactor `ChannelView`, removing a bunch of clang-tidy warnings. (#4926)
|
||||
|
@ -92,13 +105,24 @@
|
|||
- Dev: Added Tests for Windows and MacOS in CI. (#4970, #5032)
|
||||
- Dev: Move `clang-tidy` checker to its own CI job. (#4996)
|
||||
- Dev: Refactored the Image Uploader feature. (#4971)
|
||||
- Dev: Refactored the SplitOverlay code. (#5082)
|
||||
- Dev: Refactored the TwitchBadges structure, making it less of a singleton. (#5096)
|
||||
- Dev: Moved the Network files to their own folder. (#5089)
|
||||
- Dev: Fixed deadlock and use-after-free in tests. (#4981)
|
||||
- Dev: Moved all `.clang-format` files to the root directory. (#5037)
|
||||
- Dev: Load less message history upon reconnects. (#5001, #5018)
|
||||
- Dev: Load less message history upon reconnects. (#5001)
|
||||
- Dev: Removed the `NullablePtr` class. (#5091)
|
||||
- Dev: BREAKING: Replace custom `import()` with normal Lua `require()`. (#5014)
|
||||
- Dev: Fixed most compiler warnings. (#5028)
|
||||
- Dev: Added the ability to show `ChannelView`s without a `Split`. (#4747)
|
||||
- Dev: Refactor Args to be less of a singleton. (#5041)
|
||||
- Dev: Channels without any animated elements on screen will skip updates from the GIF timer. (#5042, #5043, #5045)
|
||||
- Dev: Autogenerate docs/plugin-meta.lua. (#5055)
|
||||
- Dev: Refactor `NetworkPrivate`. (#5063)
|
||||
- Dev: Refactor `Paths` & `Updates`, focusing on reducing their singletoniability. (#5092)
|
||||
- Dev: Removed duplicate scale in settings dialog. (#5069)
|
||||
- Dev: Fix `NotebookTab` emitting updates for every message. (#5068)
|
||||
- Dev: Added benchmark for parsing and building recent messages. (#5071)
|
||||
|
||||
## 2.4.6
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
project(chatterino-benchmark)
|
||||
|
||||
set(benchmark_SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/Emojis.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/Highlights.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/FormatTime.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/Helpers.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/LimitedQueue.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/LinkParser.cpp
|
||||
src/main.cpp
|
||||
resources/bench.qrc
|
||||
|
||||
src/Emojis.cpp
|
||||
src/Highlights.cpp
|
||||
src/FormatTime.cpp
|
||||
src/Helpers.cpp
|
||||
src/LimitedQueue.cpp
|
||||
src/LinkParser.cpp
|
||||
src/RecentMessages.cpp
|
||||
# Add your new file above this line!
|
||||
)
|
||||
|
||||
|
@ -27,4 +30,5 @@ set_target_properties(${PROJECT_NAME}
|
|||
RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin"
|
||||
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin"
|
||||
AUTORCC ON
|
||||
)
|
||||
|
|
6
benchmarks/resources/bench.qrc
Normal file
6
benchmarks/resources/bench.qrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/bench">
|
||||
<file>recentmessages-nymn.json</file>
|
||||
<file>seventvemotes-nymn.json</file>
|
||||
</qresource>
|
||||
</RCC>
|
1
benchmarks/resources/recentmessages-nymn.json
Normal file
1
benchmarks/resources/recentmessages-nymn.json
Normal file
File diff suppressed because one or more lines are too long
1
benchmarks/resources/seventvemotes-nymn.json
Normal file
1
benchmarks/resources/seventvemotes-nymn.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -145,16 +145,13 @@ static void BM_EmojiParsing(benchmark::State &state)
|
|||
|
||||
BENCHMARK(BM_EmojiParsing);
|
||||
|
||||
template <class... Args>
|
||||
static void BM_EmojiParsing2(benchmark::State &state, Args &&...args)
|
||||
static void BM_EmojiParsing2(benchmark::State &state, const QString &input,
|
||||
int expectedNumEmojis)
|
||||
{
|
||||
Emojis emojis;
|
||||
|
||||
emojis.load();
|
||||
|
||||
auto argsTuple = std::make_tuple(std::move(args)...);
|
||||
auto input = std::get<0>(argsTuple);
|
||||
auto expectedNumEmojis = std::get<1>(argsTuple);
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto output = emojis.parse(input);
|
||||
|
|
|
@ -4,35 +4,41 @@
|
|||
|
||||
using namespace chatterino;
|
||||
|
||||
template <class... Args>
|
||||
void BM_TimeFormatting(benchmark::State &state, Args &&...args)
|
||||
void BM_TimeFormattingQString(benchmark::State &state, const QString &v)
|
||||
{
|
||||
auto args_tuple = std::make_tuple(std::move(args)...);
|
||||
for (auto _ : state)
|
||||
{
|
||||
formatTime(std::get<0>(args_tuple));
|
||||
formatTime(v);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 0, 0);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs0, "0");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 1337, 1337);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs1337, "1337");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 623452, 623452);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs623452, "623452");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 8345, 8345);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs8345, "8345");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 314034, 314034);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs314034, "314034");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 27, 27);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs27, "27");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 34589, 34589);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs34589, "34589");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 3659, 3659);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs3659, "3659");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 1045345, 1045345);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs1045345, "1045345");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, 86432, 86432);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qs86432, "86432");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qsempty, "");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormatting, qsinvalid, "asd");
|
||||
void BM_TimeFormattingInt(benchmark::State &state, int v)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
formatTime(v);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 0, 0);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 1045345, 1045345);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 1337, 1337);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 27, 27);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 314034, 314034);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 34589, 34589);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 3659, 3659);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 623452, 623452);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 8345, 8345);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingInt, 86432, 86432);
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs0, "0");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs1045345, "1045345");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs1337, "1337");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs27, "27");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs314034, "314034");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs34589, "34589");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs3659, "3659");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs623452, "623452");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs8345, "8345");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qs86432, "86432");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qsempty, "");
|
||||
BENCHMARK_CAPTURE(BM_TimeFormattingQString, qsinvalid, "asd");
|
||||
|
|
223
benchmarks/src/RecentMessages.cpp
Normal file
223
benchmarks/src/RecentMessages.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
#include "common/Literals.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "mocks/EmptyApplication.hpp"
|
||||
#include "mocks/TwitchIrcServer.hpp"
|
||||
#include "mocks/UserData.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||
#include "providers/ffz/FfzBadges.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/recentmessages/Impl.hpp"
|
||||
#include "providers/seventv/SeventvBadges.hpp"
|
||||
#include "providers/seventv/SeventvEmotes.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QString>
|
||||
|
||||
#include <optional>
|
||||
|
||||
using namespace chatterino;
|
||||
using namespace literals;
|
||||
|
||||
namespace {
|
||||
|
||||
class MockApplication : mock::EmptyApplication
|
||||
{
|
||||
public:
|
||||
IEmotes *getEmotes() override
|
||||
{
|
||||
return &this->emotes;
|
||||
}
|
||||
|
||||
IUserDataController *getUserData() override
|
||||
{
|
||||
return &this->userData;
|
||||
}
|
||||
|
||||
AccountController *getAccounts() override
|
||||
{
|
||||
return &this->accounts;
|
||||
}
|
||||
|
||||
ITwitchIrcServer *getTwitch() override
|
||||
{
|
||||
return &this->twitch;
|
||||
}
|
||||
|
||||
ChatterinoBadges *getChatterinoBadges() override
|
||||
{
|
||||
return &this->chatterinoBadges;
|
||||
}
|
||||
|
||||
FfzBadges *getFfzBadges() override
|
||||
{
|
||||
return &this->ffzBadges;
|
||||
}
|
||||
|
||||
SeventvBadges *getSeventvBadges() override
|
||||
{
|
||||
return &this->seventvBadges;
|
||||
}
|
||||
|
||||
HighlightController *getHighlights() override
|
||||
{
|
||||
return &this->highlights;
|
||||
}
|
||||
|
||||
AccountController accounts;
|
||||
Emotes emotes;
|
||||
mock::UserDataController userData;
|
||||
mock::MockTwitchIrcServer twitch;
|
||||
ChatterinoBadges chatterinoBadges;
|
||||
FfzBadges ffzBadges;
|
||||
SeventvBadges seventvBadges;
|
||||
HighlightController highlights;
|
||||
};
|
||||
|
||||
std::optional<QJsonDocument> tryReadJsonFile(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonParseError e;
|
||||
auto doc = QJsonDocument::fromJson(file.readAll(), &e);
|
||||
if (e.error != QJsonParseError::NoError)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
QJsonDocument readJsonFile(const QString &path)
|
||||
{
|
||||
auto opt = tryReadJsonFile(path);
|
||||
if (!opt)
|
||||
{
|
||||
_exit(1);
|
||||
}
|
||||
return *opt;
|
||||
}
|
||||
|
||||
class RecentMessages
|
||||
{
|
||||
public:
|
||||
explicit RecentMessages(const QString &name_)
|
||||
: name(name_)
|
||||
, chan(this->name)
|
||||
{
|
||||
const auto seventvEmotes =
|
||||
tryReadJsonFile(u":/bench/seventvemotes-%1.json"_s.arg(this->name));
|
||||
const auto bttvEmotes =
|
||||
tryReadJsonFile(u":/bench/bttvemotes-%1.json"_s.arg(this->name));
|
||||
const auto ffzEmotes =
|
||||
tryReadJsonFile(u":/bench/ffzemotes-%1.json"_s.arg(this->name));
|
||||
|
||||
if (seventvEmotes)
|
||||
{
|
||||
this->chan.setSeventvEmotes(
|
||||
std::make_shared<const EmoteMap>(seventv::detail::parseEmotes(
|
||||
seventvEmotes->object()["emote_set"_L1]
|
||||
.toObject()["emotes"_L1]
|
||||
.toArray(),
|
||||
false)));
|
||||
}
|
||||
|
||||
if (bttvEmotes)
|
||||
{
|
||||
this->chan.setBttvEmotes(std::make_shared<const EmoteMap>(
|
||||
bttv::detail::parseChannelEmotes(bttvEmotes->object(),
|
||||
this->name)));
|
||||
}
|
||||
|
||||
if (ffzEmotes)
|
||||
{
|
||||
this->chan.setFfzEmotes(std::make_shared<const EmoteMap>(
|
||||
ffz::detail::parseChannelEmotes(ffzEmotes->object())));
|
||||
}
|
||||
|
||||
this->messages =
|
||||
readJsonFile(u":/bench/recentmessages-%1.json"_s.arg(this->name));
|
||||
}
|
||||
|
||||
~RecentMessages()
|
||||
{
|
||||
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||
}
|
||||
|
||||
virtual void run(benchmark::State &state) = 0;
|
||||
|
||||
protected:
|
||||
QString name;
|
||||
MockApplication app;
|
||||
TwitchChannel chan;
|
||||
QJsonDocument messages;
|
||||
};
|
||||
|
||||
class ParseRecentMessages : public RecentMessages
|
||||
{
|
||||
public:
|
||||
explicit ParseRecentMessages(const QString &name_)
|
||||
: RecentMessages(name_)
|
||||
{
|
||||
}
|
||||
|
||||
void run(benchmark::State &state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto parsed = recentmessages::detail::parseRecentMessages(
|
||||
this->messages.object());
|
||||
benchmark::DoNotOptimize(parsed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BuildRecentMessages : public RecentMessages
|
||||
{
|
||||
public:
|
||||
explicit BuildRecentMessages(const QString &name_)
|
||||
: RecentMessages(name_)
|
||||
{
|
||||
}
|
||||
|
||||
void run(benchmark::State &state)
|
||||
{
|
||||
auto parsed = recentmessages::detail::parseRecentMessages(
|
||||
this->messages.object());
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto built = recentmessages::detail::buildRecentMessages(
|
||||
parsed, &this->chan);
|
||||
benchmark::DoNotOptimize(built);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void BM_ParseRecentMessages(benchmark::State &state, const QString &name)
|
||||
{
|
||||
ParseRecentMessages bench(name);
|
||||
bench.run(state);
|
||||
}
|
||||
|
||||
void BM_BuildRecentMessages(benchmark::State &state, const QString &name)
|
||||
{
|
||||
BuildRecentMessages bench(name);
|
||||
bench.run(state);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BENCHMARK_CAPTURE(BM_ParseRecentMessages, nymn, u"nymn"_s);
|
||||
BENCHMARK_CAPTURE(BM_BuildRecentMessages, nymn, u"nymn"_s);
|
|
@ -1,3 +1,4 @@
|
|||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
@ -11,6 +12,8 @@ int main(int argc, char **argv)
|
|||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
initResources();
|
||||
|
||||
::benchmark::Initialize(&argc, argv);
|
||||
|
||||
// Ensure settings are initialized before any benchmarks are run
|
||||
|
|
4
docs/chatterino.d.ts
vendored
4
docs/chatterino.d.ts
vendored
|
@ -26,7 +26,7 @@ declare module c2 {
|
|||
}
|
||||
|
||||
enum EventType {
|
||||
RegisterCompletions = "RegisterCompletions",
|
||||
CompletionRequested = "CompletionRequested",
|
||||
}
|
||||
|
||||
type CbFuncCompletionsRequested = (
|
||||
|
@ -35,7 +35,7 @@ declare module c2 {
|
|||
cursor_position: number,
|
||||
is_first_word: boolean
|
||||
) => CompletionList;
|
||||
type CbFunc<T> = T extends EventType.RegisterCompletions
|
||||
type CbFunc<T> = T extends EventType.CompletionRequested
|
||||
? CbFuncCompletionsRequested
|
||||
: never;
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@
|
|||
"type": "string",
|
||||
"description": "A small description of your license.",
|
||||
"examples": ["MIT", "GPL-2.0-or-later"]
|
||||
}
|
||||
},
|
||||
"$schema": { "type": "string" }
|
||||
},
|
||||
"required": ["name", "description", "authors", "version", "license"]
|
||||
}
|
||||
|
|
58
docs/plugin-meta.lua
Normal file
58
docs/plugin-meta.lua
Normal file
|
@ -0,0 +1,58 @@
|
|||
---@meta Chatterino2
|
||||
|
||||
-- This file is automatically generated from src/controllers/plugins/LuaAPI.hpp by the scripts/make_luals_meta.py script
|
||||
-- This file is intended to be used with LuaLS (https://luals.github.io/).
|
||||
-- Add the folder this file is in to "Lua.workspace.library".
|
||||
|
||||
c2 = {}
|
||||
|
||||
---@alias LogLevel integer
|
||||
---@type { Debug: LogLevel, Info: LogLevel, Warning: LogLevel, Critical: LogLevel }
|
||||
c2.LogLevel = {}
|
||||
|
||||
---@alias EventType integer
|
||||
---@type { CompletionRequested: EventType }
|
||||
c2.EventType = {}
|
||||
---@class CommandContext
|
||||
---@field words string[] The words typed when executing the command. For example `/foo bar baz` will result in `{"/foo", "bar", "baz"}`.
|
||||
---@field channel_name string The name of the channel the command was executed in.
|
||||
|
||||
---@class CompletionList
|
||||
---@field values string[] The completions
|
||||
---@field hide_others boolean Whether other completions from Chatterino should be hidden/ignored.
|
||||
|
||||
--- Registers a new command called `name` which when executed will call `handler`.
|
||||
---
|
||||
---@param name string The name of the command.
|
||||
---@param handler fun(ctx: CommandContext) The handler to be invoked when the command gets executed.
|
||||
---@return boolean ok Returns `true` if everything went ok, `false` if a command with this name exists.
|
||||
function c2.register_command(name, handler) end
|
||||
|
||||
--- Registers a callback to be invoked when completions for a term are requested.
|
||||
---
|
||||
---@param type "CompletionRequested"
|
||||
---@param func fun(query: string, full_text_content: string, cursor_position: integer, is_first_word: boolean): CompletionList The callback to be invoked.
|
||||
function c2.register_callback(type, func) end
|
||||
|
||||
--- Sends a message to `channel` with the specified text. Also executes commands.
|
||||
---
|
||||
--- **Warning**: It is possible to trigger your own Lua command with this causing a potentially infinite loop.
|
||||
---
|
||||
---@param channel string The name of the Twitch channel
|
||||
---@param text string The text to be sent
|
||||
---@return boolean ok
|
||||
function c2.send_msg(channel, text) end
|
||||
|
||||
--- Creates a system message (gray message) and adds it to the Twitch channel specified by `channel`.
|
||||
---
|
||||
---@param channel string
|
||||
---@param text string
|
||||
---@return boolean ok
|
||||
function c2.system_msg(channel, text) end
|
||||
|
||||
--- Writes a message to the Chatterino log.
|
||||
---
|
||||
---@param level LogLevel The desired level.
|
||||
---@param ... any Values to log. Should be convertible to a string with `tostring()`.
|
||||
function c2.log(level, ...) end
|
||||
|
|
@ -113,6 +113,42 @@ Limitations/known issues:
|
|||
rebuilding the window content caused by reloading another plugin will solve this.
|
||||
- Spaces in command names aren't handled very well (https://github.com/Chatterino/chatterino2/issues/1517).
|
||||
|
||||
#### `register_callback("CompletionRequested", handler)`
|
||||
|
||||
Registers a callback (`handler`) to process completions. The callback gets the following parameters:
|
||||
|
||||
- `query`: The queried word.
|
||||
- `full_text_content`: The whole input.
|
||||
- `cursor_position`: The position of the cursor in the input.
|
||||
- `is_first_word`: Flag whether `query` is the first word in the input.
|
||||
|
||||
Example:
|
||||
|
||||
| Input | `query` | `full_text_content` | `cursor_position` | `is_first_word` |
|
||||
| ---------- | ------- | ------------------- | ----------------- | --------------- |
|
||||
| `foo│` | `foo` | `foo` | 3 | `true` |
|
||||
| `fo│o` | `fo` | `foo` | 2 | `true` |
|
||||
| `foo bar│` | `bar` | `foo bar` | 7 | `false` |
|
||||
| `foo │bar` | `foo` | `foo bar` | 4 | `false` |
|
||||
|
||||
```lua
|
||||
function string.startswith(s, other)
|
||||
return string.sub(s, 1, string.len(other)) == other
|
||||
end
|
||||
|
||||
c2.register_callback(
|
||||
"CompletionRequested",
|
||||
function(query, full_text_content, cursor_position, is_first_word)
|
||||
if ("!join"):startswith(query) then
|
||||
---@type CompletionList
|
||||
return { hide_others = true, values = { "!join" } }
|
||||
end
|
||||
---@type CompletionList
|
||||
return { hide_others = false, values = {} }
|
||||
end
|
||||
)
|
||||
```
|
||||
|
||||
#### `send_msg(channel, text)`
|
||||
|
||||
Sends a message to `channel` with the specified text. Also executes commands.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit bbf0a34260a3e8d6e6c48be57653840ac3fa8c30
|
||||
Subproject commit 17946d65a41a72b447da37df6e314cded9650c32
|
|
@ -1 +1 @@
|
|||
Subproject commit f92bc7bc4940bf58b7f03cefa81a78ef09752007
|
||||
Subproject commit 87ed4d950319d8da1191431f5c8c84d1fdcb92a5
|
|
@ -1 +1 @@
|
|||
Subproject commit ca452a811d684db42f93d6352301406754d0c536
|
||||
Subproject commit d06770649a7e83db780865d09c313a876bf0f4eb
|
|
@ -1,81 +1,150 @@
|
|||
#pragma once
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Args.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Updates.hpp"
|
||||
|
||||
namespace chatterino::mock {
|
||||
|
||||
class EmptyApplication : public IApplication
|
||||
{
|
||||
public:
|
||||
EmptyApplication()
|
||||
: updates_(this->paths_)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~EmptyApplication() = default;
|
||||
|
||||
const Paths &getPaths() override
|
||||
{
|
||||
return this->paths_;
|
||||
}
|
||||
|
||||
const Args &getArgs() override
|
||||
{
|
||||
return this->args_;
|
||||
}
|
||||
|
||||
Theme *getThemes() override
|
||||
{
|
||||
assert(
|
||||
false &&
|
||||
"EmptyApplication::getThemes was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Fonts *getFonts() override
|
||||
{
|
||||
assert(
|
||||
false &&
|
||||
"EmptyApplication::getFonts was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IEmotes *getEmotes() override
|
||||
{
|
||||
assert(
|
||||
false &&
|
||||
"EmptyApplication::getEmotes was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AccountController *getAccounts() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getAccounts was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HotkeyController *getHotkeys() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getHotkeys was called without being "
|
||||
"initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WindowManager *getWindows() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getWindows was called without being "
|
||||
"initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Toasts *getToasts() override
|
||||
{
|
||||
assert(
|
||||
false &&
|
||||
"EmptyApplication::getToasts was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CrashHandler *getCrashHandler() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getCrashHandler was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CommandController *getCommands() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getCommands was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NotificationController *getNotifications() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getNotifications was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HighlightController *getHighlights() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getHighlights was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ITwitchIrcServer *getTwitch() override
|
||||
{
|
||||
assert(
|
||||
false &&
|
||||
"EmptyApplication::getTwitch was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PubSub *getTwitchPubSub() override
|
||||
{
|
||||
assert(false && "getTwitchPubSub was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TwitchBadges *getTwitchBadges() override
|
||||
{
|
||||
assert(false && "getTwitchBadges was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Logging *getChatLogger() override
|
||||
{
|
||||
assert(!"getChatLogger was called without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ChatterinoBadges *getChatterinoBadges() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getChatterinoBadges was called "
|
||||
"without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FfzBadges *getFfzBadges() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getFfzBadges was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -87,6 +156,8 @@ public:
|
|||
|
||||
IUserDataController *getUserData() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getUserData was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -98,11 +169,15 @@ public:
|
|||
|
||||
ITwitchLiveController *getTwitchLiveController() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getTwitchLiveController was called "
|
||||
"without being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ImageUploader *getImageUploader() override
|
||||
{
|
||||
assert(false && "EmptyApplication::getImageUploader was called without "
|
||||
"being initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -110,6 +185,16 @@ public:
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Updates &getUpdates() override
|
||||
{
|
||||
return this->updates_;
|
||||
}
|
||||
|
||||
private:
|
||||
Paths paths_;
|
||||
Args args_;
|
||||
Updates updates_;
|
||||
};
|
||||
|
||||
} // namespace chatterino::mock
|
||||
|
|
BIN
resources/avatars/fraxx.png
Normal file
BIN
resources/avatars/fraxx.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 912 B |
|
@ -66,6 +66,8 @@ olafyang | https://github.com/olafyang | | Contributor
|
|||
chrrs | https://github.com/chrrs | | Contributor
|
||||
4rneee | https://github.com/4rneee | | Contributor
|
||||
crazysmc | https://github.com/crazysmc | :/avatars/crazysmc.png | Contributor
|
||||
SputNikPlop | https://github.com/SputNikPlop | | Contributor
|
||||
fraxx | https://github.com/fraxxio | :/avatars/fraxx.png | Contributor
|
||||
|
||||
# If you are a contributor add yourself above this line
|
||||
|
||||
|
|
12
scripts/check-clang-tidy.sh
Executable file
12
scripts/check-clang-tidy.sh
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
clang-tidy --version
|
||||
|
||||
find \
|
||||
src/ \
|
||||
tests/src/ \
|
||||
benchmarks/src/ \
|
||||
mocks/include/ \
|
||||
-type f \( -name "*.hpp" -o -name "*.cpp" \) -print0 | parallel -0 -j16 -I {} clang-tidy --quiet "$@" "{}"
|
142
scripts/make_luals_meta.py
Normal file
142
scripts/make_luals_meta.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
"""
|
||||
This script generates docs/plugin-meta.lua. It accepts no arguments
|
||||
|
||||
It assumes comments look like:
|
||||
/**
|
||||
* Thing
|
||||
*
|
||||
* @lua@param thing boolean
|
||||
* @lua@returns boolean
|
||||
* @exposed name
|
||||
*/
|
||||
- Do not have any useful info on '/**' and '*/' lines.
|
||||
- Class members are not allowed to have non-@command lines and commands different from @lua@field
|
||||
|
||||
Valid commands are:
|
||||
1. @exposeenum [dotted.name.in_lua.last_part]
|
||||
Define a table with keys of the enum. Values behind those keys aren't
|
||||
written on purpose.
|
||||
This generates three lines:
|
||||
- An type alias of [last_part] to integer,
|
||||
- A type description that describes available values of the enum,
|
||||
- A global table definition for the num
|
||||
2. @lua[@command]
|
||||
Writes [@command] to the file as a comment, usually this is @class, @param, @return, ...
|
||||
@lua@class and @lua@field have special treatment when it comes to generation of spacing new lines
|
||||
3. @exposed [c2.name]
|
||||
Generates a function definition line from the last `@lua@param`s.
|
||||
|
||||
Non-command lines of comments are written with a space after '---'
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
BOILERPLATE = """
|
||||
---@meta Chatterino2
|
||||
|
||||
-- This file is automatically generated from src/controllers/plugins/LuaAPI.hpp by the scripts/make_luals_meta.py script
|
||||
-- This file is intended to be used with LuaLS (https://luals.github.io/).
|
||||
-- Add the folder this file is in to "Lua.workspace.library".
|
||||
|
||||
c2 = {}
|
||||
"""
|
||||
|
||||
repo_root = Path(__file__).parent.parent
|
||||
lua_api_file = repo_root / "src" / "controllers" / "plugins" / "LuaAPI.hpp"
|
||||
lua_meta = repo_root / "docs" / "plugin-meta.lua"
|
||||
|
||||
print("Reading from", lua_api_file.relative_to(repo_root))
|
||||
print("Writing to", lua_meta.relative_to(repo_root))
|
||||
with lua_api_file.open("r") as f:
|
||||
lines = f.read().splitlines()
|
||||
|
||||
# Are we in a doc comment?
|
||||
comment: bool = False
|
||||
|
||||
# Last `@lua@param`s seen - for @exposed generation
|
||||
last_params_names: list[str] = []
|
||||
# Are we in a `@lua@class` definition? - makes newlines around @lua@class and @lua@field prettier
|
||||
is_class = False
|
||||
|
||||
# The name of the next enum in lua world
|
||||
expose_next_enum_as: str | None = None
|
||||
# Name of the current enum in c++ world, used to generate internal typenames for
|
||||
current_enum_name: str | None = None
|
||||
|
||||
with lua_meta.open("w") as out:
|
||||
out.write(BOILERPLATE[1:]) # skip the newline after triple quote
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith("enum class "):
|
||||
line = line.removeprefix("enum class ")
|
||||
temp = line.split(" ", 2)
|
||||
current_enum_name = temp[0]
|
||||
if not expose_next_enum_as:
|
||||
print(
|
||||
f"Skipping enum {current_enum_name}, there wasn't a @exposeenum command"
|
||||
)
|
||||
current_enum_name = None
|
||||
continue
|
||||
current_enum_name = expose_next_enum_as.split(".", 1)[-1]
|
||||
out.write("---@alias " + current_enum_name + " integer\n")
|
||||
out.write("---@type { ")
|
||||
# temp[1] is '{'
|
||||
if len(temp) == 2: # no values on this line
|
||||
continue
|
||||
line = temp[2]
|
||||
|
||||
if current_enum_name is not None:
|
||||
for i, tok in enumerate(line.split(" ")):
|
||||
if tok == "};":
|
||||
break
|
||||
entry = tok.removesuffix(",")
|
||||
if i != 0:
|
||||
out.write(", ")
|
||||
out.write(entry + ": " + current_enum_name)
|
||||
out.write(" }\n" f"{expose_next_enum_as} = {{}}\n")
|
||||
print(f"Wrote enum {expose_next_enum_as} => {current_enum_name}")
|
||||
current_enum_name = None
|
||||
expose_next_enum_as = None
|
||||
continue
|
||||
|
||||
if line.startswith("/**"):
|
||||
comment = True
|
||||
continue
|
||||
elif "*/" in line:
|
||||
comment = False
|
||||
if not is_class:
|
||||
out.write("\n")
|
||||
continue
|
||||
if not comment:
|
||||
continue
|
||||
line = line.replace("*", "", 1).lstrip()
|
||||
if line == "":
|
||||
out.write("---\n")
|
||||
elif line.startswith("@exposeenum "):
|
||||
expose_next_enum_as = line.split(" ", 1)[1]
|
||||
elif line.startswith("@exposed "):
|
||||
exp = line.replace("@exposed ", "", 1)
|
||||
params = ", ".join(last_params_names)
|
||||
out.write(f"function {exp}({params}) end\n")
|
||||
print(f"Wrote function {exp}(...)")
|
||||
last_params_names = []
|
||||
elif line.startswith("@lua"):
|
||||
command = line.replace("@lua", "", 1)
|
||||
if command.startswith("@param"):
|
||||
last_params_names.append(command.split(" ", 2)[1])
|
||||
elif command.startswith("@class"):
|
||||
print(f"Writing {command}")
|
||||
if is_class:
|
||||
out.write("\n")
|
||||
is_class = True
|
||||
elif not command.startswith("@field"):
|
||||
is_class = False
|
||||
|
||||
out.write("---" + command + "\n")
|
||||
else:
|
||||
if is_class:
|
||||
is_class = False
|
||||
out.write("\n")
|
||||
|
||||
# note the space difference from the branch above
|
||||
out.write("--- " + line + "\n")
|
|
@ -12,6 +12,7 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/sound/ISoundController.hpp"
|
||||
#include "providers/seventv/SeventvAPI.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
#include "singletons/ImageUploader.hpp"
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/PluginController.hpp"
|
||||
|
@ -35,6 +36,7 @@
|
|||
#include "providers/twitch/PubSubActions.hpp"
|
||||
#include "providers/twitch/PubSubManager.hpp"
|
||||
#include "providers/twitch/PubSubMessages.hpp"
|
||||
#include "providers/twitch/pubsubmessages/LowTrustUsers.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||
|
@ -86,6 +88,8 @@ ISoundController *makeSoundController(Settings &settings)
|
|||
}
|
||||
}
|
||||
|
||||
const QString TWITCH_PUBSUB_URL = "wss://pubsub-edge.twitch.tv";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -104,17 +108,20 @@ IApplication::IApplication()
|
|||
// It will create the instances of the major classes, and connect their signals
|
||||
// to each other
|
||||
|
||||
Application::Application(Settings &_settings, Paths &_paths)
|
||||
: themes(&this->emplace<Theme>())
|
||||
Application::Application(Settings &_settings, const Paths &paths,
|
||||
const Args &_args, Updates &_updates)
|
||||
: paths_(paths)
|
||||
, args_(_args)
|
||||
, themes(&this->emplace<Theme>())
|
||||
, fonts(&this->emplace<Fonts>())
|
||||
, emotes(&this->emplace<Emotes>())
|
||||
, accounts(&this->emplace<AccountController>())
|
||||
, hotkeys(&this->emplace<HotkeyController>())
|
||||
, windows(&this->emplace<WindowManager>())
|
||||
, windows(&this->emplace(new WindowManager(paths)))
|
||||
, toasts(&this->emplace<Toasts>())
|
||||
, imageUploader(&this->emplace<ImageUploader>())
|
||||
, seventvAPI(&this->emplace<SeventvAPI>())
|
||||
, crashHandler(&this->emplace<CrashHandler>())
|
||||
, crashHandler(&this->emplace(new CrashHandler(paths)))
|
||||
|
||||
, commands(&this->emplace<CommandController>())
|
||||
, notifications(&this->emplace<NotificationController>())
|
||||
|
@ -123,15 +130,18 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||
, ffzBadges(&this->emplace<FfzBadges>())
|
||||
, seventvBadges(&this->emplace<SeventvBadges>())
|
||||
, userData(&this->emplace<UserDataController>())
|
||||
, userData(&this->emplace(new UserDataController(paths)))
|
||||
, sound(&this->emplace<ISoundController>(makeSoundController(_settings)))
|
||||
, twitchLiveController(&this->emplace<TwitchLiveController>())
|
||||
, twitchPubSub(new PubSub(TWITCH_PUBSUB_URL))
|
||||
, twitchBadges(new TwitchBadges)
|
||||
, logging(new Logging(_settings))
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
, plugins(&this->emplace<PluginController>())
|
||||
, plugins(&this->emplace(new PluginController(paths)))
|
||||
#endif
|
||||
, logging(&this->emplace<Logging>())
|
||||
, updates(_updates)
|
||||
{
|
||||
this->instance = this;
|
||||
Application::instance = this;
|
||||
|
||||
// We can safely ignore this signal's connection since the Application will always
|
||||
// be destroyed after fonts
|
||||
|
@ -140,17 +150,25 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||
});
|
||||
}
|
||||
|
||||
void Application::initialize(Settings &settings, Paths &paths)
|
||||
Application::~Application() = default;
|
||||
|
||||
void Application::fakeDtor()
|
||||
{
|
||||
this->twitchPubSub.reset();
|
||||
this->twitchBadges.reset();
|
||||
}
|
||||
|
||||
void Application::initialize(Settings &settings, const Paths &paths)
|
||||
{
|
||||
assert(isAppInitialized == false);
|
||||
isAppInitialized = true;
|
||||
|
||||
// Show changelog
|
||||
if (!getArgs().isFramelessEmbed &&
|
||||
if (!this->args_.isFramelessEmbed &&
|
||||
getSettings()->currentVersion.getValue() != "" &&
|
||||
getSettings()->currentVersion.getValue() != CHATTERINO_VERSION)
|
||||
{
|
||||
auto box = new QMessageBox(QMessageBox::Information, "Chatterino 2",
|
||||
auto *box = new QMessageBox(QMessageBox::Information, "Chatterino 2",
|
||||
"Show changelog?",
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
box->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
@ -161,7 +179,7 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||
}
|
||||
}
|
||||
|
||||
if (!getArgs().isFramelessEmbed)
|
||||
if (!this->args_.isFramelessEmbed)
|
||||
{
|
||||
getSettings()->currentVersion.setValue(CHATTERINO_VERSION);
|
||||
|
||||
|
@ -179,12 +197,12 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||
// Show crash message.
|
||||
// On Windows, the crash message was already shown.
|
||||
#ifndef Q_OS_WIN
|
||||
if (!getArgs().isFramelessEmbed && getArgs().crashRecovery)
|
||||
if (!this->args_.isFramelessEmbed && this->args_.crashRecovery)
|
||||
{
|
||||
if (auto selected =
|
||||
if (auto *selected =
|
||||
this->windows->getMainWindow().getNotebook().getSelectedPage())
|
||||
{
|
||||
if (auto container = dynamic_cast<SplitContainer *>(selected))
|
||||
if (auto *container = dynamic_cast<SplitContainer *>(selected))
|
||||
{
|
||||
for (auto &&split : container->getSplits())
|
||||
{
|
||||
|
@ -203,7 +221,7 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||
|
||||
this->windows->updateWordTypeMask();
|
||||
|
||||
if (!getArgs().isFramelessEmbed)
|
||||
if (!this->args_.isFramelessEmbed)
|
||||
{
|
||||
this->initNm(paths);
|
||||
}
|
||||
|
@ -219,14 +237,14 @@ int Application::run(QApplication &qtApp)
|
|||
|
||||
this->twitch->connect();
|
||||
|
||||
if (!getArgs().isFramelessEmbed)
|
||||
if (!this->args_.isFramelessEmbed)
|
||||
{
|
||||
this->windows->getMainWindow().show();
|
||||
}
|
||||
|
||||
getSettings()->betaUpdates.connect(
|
||||
[] {
|
||||
Updates::instance().checkForUpdates();
|
||||
[this] {
|
||||
this->updates.checkForUpdates();
|
||||
},
|
||||
false);
|
||||
|
||||
|
@ -305,11 +323,28 @@ ITwitchLiveController *Application::getTwitchLiveController()
|
|||
return this->twitchLiveController;
|
||||
}
|
||||
|
||||
TwitchBadges *Application::getTwitchBadges()
|
||||
{
|
||||
assert(this->twitchBadges);
|
||||
|
||||
return this->twitchBadges.get();
|
||||
}
|
||||
|
||||
ITwitchIrcServer *Application::getTwitch()
|
||||
{
|
||||
return this->twitch;
|
||||
}
|
||||
|
||||
PubSub *Application::getTwitchPubSub()
|
||||
{
|
||||
return this->twitchPubSub.get();
|
||||
}
|
||||
|
||||
Logging *Application::getChatLogger()
|
||||
{
|
||||
return this->logging.get();
|
||||
}
|
||||
|
||||
void Application::save()
|
||||
{
|
||||
for (auto &singleton : this->singletons_)
|
||||
|
@ -318,7 +353,7 @@ void Application::save()
|
|||
}
|
||||
}
|
||||
|
||||
void Application::initNm(Paths &paths)
|
||||
void Application::initNm(const Paths &paths)
|
||||
{
|
||||
(void)paths;
|
||||
|
||||
|
@ -334,7 +369,7 @@ void Application::initPubSub()
|
|||
{
|
||||
// We can safely ignore these signal connections since the twitch object will always
|
||||
// be destroyed before the Application
|
||||
std::ignore = this->twitch->pubsub->signals_.moderation.chatCleared.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.chatCleared.connect(
|
||||
[this](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
|
@ -343,7 +378,7 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
QString text =
|
||||
QString("%1 cleared the chat").arg(action.source.login);
|
||||
QString("%1 cleared the chat.").arg(action.source.login);
|
||||
|
||||
auto msg = makeSystemMessage(text);
|
||||
postToThread([chan, msg] {
|
||||
|
@ -351,7 +386,7 @@ void Application::initPubSub()
|
|||
});
|
||||
});
|
||||
|
||||
std::ignore = this->twitch->pubsub->signals_.moderation.modeChanged.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.modeChanged.connect(
|
||||
[this](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
|
@ -360,7 +395,7 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
QString text =
|
||||
QString("%1 turned %2 %3 mode")
|
||||
QString("%1 turned %2 %3 mode.")
|
||||
.arg(action.source.login)
|
||||
.arg(action.state == ModeChangedAction::State::On ? "on"
|
||||
: "off")
|
||||
|
@ -377,9 +412,8 @@ void Application::initPubSub()
|
|||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.moderationStateChanged
|
||||
.connect([this](const auto &action) {
|
||||
std::ignore = this->twitchPubSub->moderation.moderationStateChanged.connect(
|
||||
[this](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
|
@ -388,7 +422,7 @@ void Application::initPubSub()
|
|||
|
||||
QString text;
|
||||
|
||||
text = QString("%1 %2 %3")
|
||||
text = QString("%1 %2 %3.")
|
||||
.arg(action.source.login,
|
||||
(action.modded ? "modded" : "unmodded"),
|
||||
action.target.login);
|
||||
|
@ -399,7 +433,7 @@ void Application::initPubSub()
|
|||
});
|
||||
});
|
||||
|
||||
std::ignore = this->twitch->pubsub->signals_.moderation.userBanned.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.userBanned.connect(
|
||||
[&](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
|
@ -414,8 +448,7 @@ void Application::initPubSub()
|
|||
chan->addOrReplaceTimeout(msg.release());
|
||||
});
|
||||
});
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.messageDeleted.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.messageDeleted.connect(
|
||||
[&](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
|
@ -439,7 +472,7 @@ void Application::initPubSub()
|
|||
|
||||
for (int i = snapshotLength - 1; i >= end; --i)
|
||||
{
|
||||
auto &s = snapshot[i];
|
||||
const auto &s = snapshot[i];
|
||||
if (!s->flags.has(MessageFlag::PubSub) &&
|
||||
s->timeoutUser == msg->timeoutUser)
|
||||
{
|
||||
|
@ -455,8 +488,7 @@ void Application::initPubSub()
|
|||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.userUnbanned.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.userUnbanned.connect(
|
||||
[&](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
|
@ -473,7 +505,95 @@ void Application::initPubSub()
|
|||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.autoModMessageCaught.connect(
|
||||
this->twitchPubSub->moderation.suspiciousMessageReceived.connect(
|
||||
[&](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious message with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
// monitored chats are received over irc; in the future, we will use pubsub instead
|
||||
if (action.treatment !=
|
||||
PubSubLowTrustUsersMessage::Treatment::Restricted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSettings()->streamerModeHideModActions &&
|
||||
isInStreamerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan =
|
||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
||||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto twitchChannel =
|
||||
std::dynamic_pointer_cast<TwitchChannel>(chan);
|
||||
if (!twitchChannel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([twitchChannel, action] {
|
||||
const auto p =
|
||||
TwitchMessageBuilder::makeLowTrustUserMessage(
|
||||
action, twitchChannel->getName(),
|
||||
twitchChannel.get());
|
||||
twitchChannel->addMessage(p.first);
|
||||
twitchChannel->addMessage(p.second);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitchPubSub->moderation.suspiciousTreatmentUpdated.connect(
|
||||
[&](const auto &action) {
|
||||
if (action.treatment ==
|
||||
PubSubLowTrustUsersMessage::Treatment::INVALID)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Received suspicious user update with unknown "
|
||||
"treatment:"
|
||||
<< action.treatmentString;
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.updatedByUserLogin.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (getSettings()->streamerModeHideModActions &&
|
||||
isInStreamerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan =
|
||||
this->twitch->getChannelOrEmptyByID(action.channelID);
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
auto msg =
|
||||
TwitchMessageBuilder::makeLowTrustUpdateMessage(action);
|
||||
chan->addMessage(msg);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore = this->twitchPubSub->moderation.autoModMessageCaught.connect(
|
||||
[&](const auto &msg, const QString &channelID) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(channelID);
|
||||
if (chan->isEmpty())
|
||||
|
@ -483,8 +603,7 @@ void Application::initPubSub()
|
|||
|
||||
switch (msg.type)
|
||||
{
|
||||
case PubSubAutoModQueueMessage::Type::
|
||||
AutoModCaughtMessage: {
|
||||
case PubSubAutoModQueueMessage::Type::AutoModCaughtMessage: {
|
||||
if (msg.status == "PENDING")
|
||||
{
|
||||
AutomodAction action(msg.data, channelID);
|
||||
|
@ -521,8 +640,7 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
// handle username style based on prefered setting
|
||||
switch (
|
||||
getSettings()->usernameDisplayMode.getValue())
|
||||
switch (getSettings()->usernameDisplayMode.getValue())
|
||||
{
|
||||
case UsernameDisplayMode::Username: {
|
||||
if (hasLocalizedName)
|
||||
|
@ -538,8 +656,7 @@ void Application::initPubSub()
|
|||
UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName)
|
||||
{
|
||||
senderDisplayName =
|
||||
QString("%1(%2)").arg(
|
||||
senderDisplayName = QString("%1(%2)").arg(
|
||||
msg.senderUserLogin,
|
||||
msg.senderUserDisplayName);
|
||||
}
|
||||
|
@ -547,12 +664,13 @@ void Application::initPubSub()
|
|||
}
|
||||
}
|
||||
|
||||
action.target = ActionUser{
|
||||
msg.senderUserID, msg.senderUserLogin,
|
||||
action.target =
|
||||
ActionUser{msg.senderUserID, msg.senderUserLogin,
|
||||
senderDisplayName, senderColor};
|
||||
postToThread([chan, action] {
|
||||
const auto p =
|
||||
makeAutomodMessage(action, chan->getName());
|
||||
TwitchMessageBuilder::makeAutomodMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first);
|
||||
chan->addMessage(p.second);
|
||||
|
||||
|
@ -574,8 +692,7 @@ void Application::initPubSub()
|
|||
}
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.autoModMessageBlocked.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.autoModMessageBlocked.connect(
|
||||
[&](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
if (chan->isEmpty())
|
||||
|
@ -584,19 +701,18 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p = makeAutomodMessage(action, chan->getName());
|
||||
const auto p = TwitchMessageBuilder::makeAutomodMessage(
|
||||
action, chan->getName());
|
||||
chan->addMessage(p.first);
|
||||
chan->addMessage(p.second);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.automodUserMessage.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.automodUserMessage.connect(
|
||||
[&](const auto &action) {
|
||||
// This condition has been set up to execute isInStreamerMode() as the last thing
|
||||
// as it could end up being expensive.
|
||||
if (getSettings()->streamerModeHideModActions &&
|
||||
isInStreamerMode())
|
||||
if (getSettings()->streamerModeHideModActions && isInStreamerMode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -615,8 +731,7 @@ void Application::initPubSub()
|
|||
chan->deleteMessage(msg->id);
|
||||
});
|
||||
|
||||
std::ignore =
|
||||
this->twitch->pubsub->signals_.moderation.automodInfoMessage.connect(
|
||||
std::ignore = this->twitchPubSub->moderation.automodInfoMessage.connect(
|
||||
[&](const auto &action) {
|
||||
auto chan = this->twitch->getChannelOrEmptyByID(action.roomID);
|
||||
|
||||
|
@ -626,13 +741,14 @@ void Application::initPubSub()
|
|||
}
|
||||
|
||||
postToThread([chan, action] {
|
||||
const auto p = makeAutomodInfoMessage(action);
|
||||
const auto p =
|
||||
TwitchMessageBuilder::makeAutomodInfoMessage(action);
|
||||
chan->addMessage(p);
|
||||
});
|
||||
});
|
||||
|
||||
std::ignore = this->twitch->pubsub->signals_.pointReward.redeemed.connect(
|
||||
[&](auto &data) {
|
||||
std::ignore =
|
||||
this->twitchPubSub->pointReward.redeemed.connect([&](auto &data) {
|
||||
QString channelId = data.value("channel_id").toString();
|
||||
if (channelId.isEmpty())
|
||||
{
|
||||
|
@ -646,35 +762,26 @@ void Application::initPubSub()
|
|||
auto reward = ChannelPointReward(data);
|
||||
|
||||
postToThread([chan, reward] {
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||
if (auto *channel = dynamic_cast<TwitchChannel *>(chan.get()))
|
||||
{
|
||||
channel->addChannelPointReward(reward);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this->twitch->pubsub->start();
|
||||
|
||||
auto RequestModerationActions = [this]() {
|
||||
this->twitch->pubsub->setAccount(
|
||||
getApp()->accounts->twitch.getCurrent());
|
||||
// TODO(pajlada): Unlisten to all authed topics instead of only
|
||||
// moderation topics this->twitch->pubsub->UnlistenAllAuthedTopics();
|
||||
|
||||
this->twitch->pubsub->listenToWhispers();
|
||||
};
|
||||
this->twitchPubSub->start();
|
||||
this->twitchPubSub->setAccount(this->accounts->twitch.getCurrent());
|
||||
|
||||
this->accounts->twitch.currentUserChanged.connect(
|
||||
[this] {
|
||||
this->twitch->pubsub->unlistenAllModerationActions();
|
||||
this->twitch->pubsub->unlistenAutomod();
|
||||
this->twitch->pubsub->unlistenWhispers();
|
||||
this->twitchPubSub->unlistenChannelModerationActions();
|
||||
this->twitchPubSub->unlistenAutomod();
|
||||
this->twitchPubSub->unlistenLowTrustUsers();
|
||||
this->twitchPubSub->unlistenChannelPointRewards();
|
||||
|
||||
this->twitchPubSub->setAccount(this->accounts->twitch.getCurrent());
|
||||
},
|
||||
boost::signals2::at_front);
|
||||
|
||||
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
|
||||
|
||||
RequestModerationActions();
|
||||
}
|
||||
|
||||
void Application::initBttvLiveUpdates()
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
class Args;
|
||||
class TwitchIrcServer;
|
||||
class ITwitchIrcServer;
|
||||
class PubSub;
|
||||
class Updates;
|
||||
|
||||
class CommandController;
|
||||
class AccountController;
|
||||
|
@ -26,6 +28,7 @@ class ISoundController;
|
|||
class SoundController;
|
||||
class ITwitchLiveController;
|
||||
class TwitchLiveController;
|
||||
class TwitchBadges;
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
class PluginController;
|
||||
#endif
|
||||
|
@ -54,6 +57,8 @@ public:
|
|||
|
||||
static IApplication *instance;
|
||||
|
||||
virtual const Paths &getPaths() = 0;
|
||||
virtual const Args &getArgs() = 0;
|
||||
virtual Theme *getThemes() = 0;
|
||||
virtual Fonts *getFonts() = 0;
|
||||
virtual IEmotes *getEmotes() = 0;
|
||||
|
@ -66,18 +71,24 @@ public:
|
|||
virtual HighlightController *getHighlights() = 0;
|
||||
virtual NotificationController *getNotifications() = 0;
|
||||
virtual ITwitchIrcServer *getTwitch() = 0;
|
||||
virtual PubSub *getTwitchPubSub() = 0;
|
||||
virtual Logging *getChatLogger() = 0;
|
||||
virtual ChatterinoBadges *getChatterinoBadges() = 0;
|
||||
virtual FfzBadges *getFfzBadges() = 0;
|
||||
virtual SeventvBadges *getSeventvBadges() = 0;
|
||||
virtual IUserDataController *getUserData() = 0;
|
||||
virtual ISoundController *getSound() = 0;
|
||||
virtual ITwitchLiveController *getTwitchLiveController() = 0;
|
||||
virtual TwitchBadges *getTwitchBadges() = 0;
|
||||
virtual ImageUploader *getImageUploader() = 0;
|
||||
virtual SeventvAPI *getSeventvAPI() = 0;
|
||||
virtual Updates &getUpdates() = 0;
|
||||
};
|
||||
|
||||
class Application : public IApplication
|
||||
{
|
||||
const Paths &paths_;
|
||||
const Args &args_;
|
||||
std::vector<std::unique_ptr<Singleton>> singletons_;
|
||||
int argc_{};
|
||||
char **argv_{};
|
||||
|
@ -85,9 +96,22 @@ class Application : public IApplication
|
|||
public:
|
||||
static Application *instance;
|
||||
|
||||
Application(Settings &settings, Paths &paths);
|
||||
Application(Settings &_settings, const Paths &paths, const Args &_args,
|
||||
Updates &_updates);
|
||||
~Application() override;
|
||||
|
||||
void initialize(Settings &settings, Paths &paths);
|
||||
Application(const Application &) = delete;
|
||||
Application(Application &&) = delete;
|
||||
Application &operator=(const Application &) = delete;
|
||||
Application &operator=(Application &&) = delete;
|
||||
|
||||
/**
|
||||
* In the interim, before we remove _exit(0); from RunGui.cpp,
|
||||
* this will destroy things we know can be destroyed
|
||||
*/
|
||||
void fakeDtor();
|
||||
|
||||
void initialize(Settings &settings, const Paths &paths);
|
||||
void load();
|
||||
void save();
|
||||
|
||||
|
@ -118,14 +142,23 @@ public:
|
|||
|
||||
private:
|
||||
TwitchLiveController *const twitchLiveController{};
|
||||
std::unique_ptr<PubSub> twitchPubSub;
|
||||
std::unique_ptr<TwitchBadges> twitchBadges;
|
||||
const std::unique_ptr<Logging> logging;
|
||||
|
||||
public:
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
PluginController *const plugins{};
|
||||
#endif
|
||||
|
||||
/*[[deprecated]]*/ Logging *const logging{};
|
||||
|
||||
const Paths &getPaths() override
|
||||
{
|
||||
return this->paths_;
|
||||
}
|
||||
const Args &getArgs() override
|
||||
{
|
||||
return this->args_;
|
||||
}
|
||||
Theme *getThemes() override
|
||||
{
|
||||
return this->themes;
|
||||
|
@ -168,6 +201,8 @@ public:
|
|||
return this->highlights;
|
||||
}
|
||||
ITwitchIrcServer *getTwitch() override;
|
||||
PubSub *getTwitchPubSub() override;
|
||||
Logging *getChatLogger() override;
|
||||
ChatterinoBadges *getChatterinoBadges() override
|
||||
{
|
||||
return this->chatterinoBadges;
|
||||
|
@ -183,6 +218,7 @@ public:
|
|||
IUserDataController *getUserData() override;
|
||||
ISoundController *getSound() override;
|
||||
ITwitchLiveController *getTwitchLiveController() override;
|
||||
TwitchBadges *getTwitchBadges() override;
|
||||
ImageUploader *getImageUploader() override
|
||||
{
|
||||
return this->imageUploader;
|
||||
|
@ -191,6 +227,10 @@ public:
|
|||
{
|
||||
return this->seventvAPI;
|
||||
}
|
||||
Updates &getUpdates() override
|
||||
{
|
||||
return this->updates;
|
||||
}
|
||||
|
||||
pajlada::Signals::NoArgSignal streamerModeChanged;
|
||||
|
||||
|
@ -199,7 +239,7 @@ private:
|
|||
void initPubSub();
|
||||
void initBttvLiveUpdates();
|
||||
void initSeventvEventAPI();
|
||||
void initNm(Paths &paths);
|
||||
void initNm(const Paths &paths);
|
||||
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
|
||||
|
@ -219,6 +259,7 @@ private:
|
|||
}
|
||||
|
||||
NativeMessagingServer nmServer{};
|
||||
Updates &updates;
|
||||
};
|
||||
|
||||
Application *getApp();
|
||||
|
|
|
@ -33,16 +33,6 @@ set(SOURCE_FILES
|
|||
common/Literals.hpp
|
||||
common/Modes.cpp
|
||||
common/Modes.hpp
|
||||
common/NetworkCommon.cpp
|
||||
common/NetworkCommon.hpp
|
||||
common/NetworkManager.cpp
|
||||
common/NetworkManager.hpp
|
||||
common/NetworkPrivate.cpp
|
||||
common/NetworkPrivate.hpp
|
||||
common/NetworkRequest.cpp
|
||||
common/NetworkRequest.hpp
|
||||
common/NetworkResult.cpp
|
||||
common/NetworkResult.hpp
|
||||
common/QLogging.cpp
|
||||
common/QLogging.hpp
|
||||
common/WindowDescriptors.cpp
|
||||
|
@ -50,6 +40,19 @@ set(SOURCE_FILES
|
|||
|
||||
common/enums/MessageOverflow.hpp
|
||||
|
||||
common/network/NetworkCommon.cpp
|
||||
common/network/NetworkCommon.hpp
|
||||
common/network/NetworkManager.cpp
|
||||
common/network/NetworkManager.hpp
|
||||
common/network/NetworkPrivate.cpp
|
||||
common/network/NetworkPrivate.hpp
|
||||
common/network/NetworkRequest.cpp
|
||||
common/network/NetworkRequest.hpp
|
||||
common/network/NetworkResult.cpp
|
||||
common/network/NetworkResult.hpp
|
||||
common/network/NetworkTask.cpp
|
||||
common/network/NetworkTask.hpp
|
||||
|
||||
controllers/accounts/Account.cpp
|
||||
controllers/accounts/Account.hpp
|
||||
controllers/accounts/AccountController.cpp
|
||||
|
@ -412,6 +415,8 @@ set(SOURCE_FILES
|
|||
providers/twitch/pubsubmessages/ChatModeratorAction.hpp
|
||||
providers/twitch/pubsubmessages/Listen.cpp
|
||||
providers/twitch/pubsubmessages/Listen.hpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.cpp
|
||||
providers/twitch/pubsubmessages/LowTrustUsers.hpp
|
||||
providers/twitch/pubsubmessages/Message.hpp
|
||||
providers/twitch/pubsubmessages/Unlisten.cpp
|
||||
providers/twitch/pubsubmessages/Unlisten.hpp
|
||||
|
@ -455,6 +460,7 @@ set(SOURCE_FILES
|
|||
singletons/helper/LoggingChannel.cpp
|
||||
singletons/helper/LoggingChannel.hpp
|
||||
|
||||
util/AbandonObject.hpp
|
||||
util/AttachToConsole.cpp
|
||||
util/AttachToConsole.hpp
|
||||
util/CancellationToken.hpp
|
||||
|
@ -588,12 +594,25 @@ set(SOURCE_FILES
|
|||
widgets/dialogs/switcher/SwitchSplitItem.cpp
|
||||
widgets/dialogs/switcher/SwitchSplitItem.hpp
|
||||
|
||||
widgets/helper/color/AlphaSlider.cpp
|
||||
widgets/helper/color/AlphaSlider.hpp
|
||||
widgets/helper/color/Checkerboard.cpp
|
||||
widgets/helper/color/Checkerboard.hpp
|
||||
widgets/helper/color/ColorButton.cpp
|
||||
widgets/helper/color/ColorButton.hpp
|
||||
widgets/helper/color/ColorInput.cpp
|
||||
widgets/helper/color/ColorInput.hpp
|
||||
widgets/helper/color/ColorItemDelegate.cpp
|
||||
widgets/helper/color/ColorItemDelegate.hpp
|
||||
widgets/helper/color/HueSlider.cpp
|
||||
widgets/helper/color/HueSlider.hpp
|
||||
widgets/helper/color/SBCanvas.cpp
|
||||
widgets/helper/color/SBCanvas.hpp
|
||||
|
||||
widgets/helper/Button.cpp
|
||||
widgets/helper/Button.hpp
|
||||
widgets/helper/ChannelView.cpp
|
||||
widgets/helper/ChannelView.hpp
|
||||
widgets/helper/ColorButton.cpp
|
||||
widgets/helper/ColorButton.hpp
|
||||
widgets/helper/ComboBoxItemDelegate.cpp
|
||||
widgets/helper/ComboBoxItemDelegate.hpp
|
||||
widgets/helper/DebugPopup.cpp
|
||||
|
@ -608,8 +627,6 @@ set(SOURCE_FILES
|
|||
widgets/helper/NotebookButton.hpp
|
||||
widgets/helper/NotebookTab.cpp
|
||||
widgets/helper/NotebookTab.hpp
|
||||
widgets/helper/QColorPicker.cpp
|
||||
widgets/helper/QColorPicker.hpp
|
||||
widgets/helper/RegExpItemDelegate.cpp
|
||||
widgets/helper/RegExpItemDelegate.hpp
|
||||
widgets/helper/TrimRegExpValidator.cpp
|
||||
|
@ -679,6 +696,7 @@ set(SOURCE_FILES
|
|||
widgets/splits/InputCompletionPopup.hpp
|
||||
widgets/splits/Split.cpp
|
||||
widgets/splits/Split.hpp
|
||||
widgets/splits/SplitCommon.hpp
|
||||
widgets/splits/SplitContainer.cpp
|
||||
widgets/splits/SplitContainer.hpp
|
||||
widgets/splits/SplitHeader.cpp
|
||||
|
@ -918,6 +936,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
|||
IRC_STATIC
|
||||
IRC_NAMESPACE=Communi
|
||||
)
|
||||
|
||||
if (USE_SYSTEM_QTKEYCHAIN)
|
||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||
CMAKE_BUILD
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "Application.hpp"
|
||||
#include "common/Args.hpp"
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/NetworkManager.hpp"
|
||||
#include "common/network/NetworkManager.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "singletons/CrashHandler.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
|
@ -98,9 +98,9 @@ namespace {
|
|||
installCustomPalette();
|
||||
}
|
||||
|
||||
void showLastCrashDialog()
|
||||
void showLastCrashDialog(const Args &args, const Paths &paths)
|
||||
{
|
||||
auto *dialog = new LastRunCrashDialog;
|
||||
auto *dialog = new LastRunCrashDialog(args, paths);
|
||||
// Use exec() over open() to block the app from being loaded
|
||||
// and to be able to set the safe mode.
|
||||
dialog->exec();
|
||||
|
@ -223,16 +223,17 @@ namespace {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
void runGui(QApplication &a, Paths &paths, Settings &settings)
|
||||
void runGui(QApplication &a, const Paths &paths, Settings &settings,
|
||||
const Args &args, Updates &updates)
|
||||
{
|
||||
initQt();
|
||||
initResources();
|
||||
initSignalHandler();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (getArgs().crashRecovery)
|
||||
if (args.crashRecovery)
|
||||
{
|
||||
showLastCrashDialog();
|
||||
showLastCrashDialog(args, paths);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -269,14 +270,14 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
|||
});
|
||||
|
||||
chatterino::NetworkManager::init();
|
||||
chatterino::Updates::instance().checkForUpdates();
|
||||
updates.checkForUpdates();
|
||||
|
||||
Application app(settings, paths);
|
||||
Application app(settings, paths, args, updates);
|
||||
app.initialize(settings, paths);
|
||||
app.run(a);
|
||||
app.save();
|
||||
|
||||
if (!getArgs().dontSaveSettings)
|
||||
if (!args.dontSaveSettings)
|
||||
{
|
||||
pajlada::Settings::SettingManager::gSave();
|
||||
}
|
||||
|
@ -288,6 +289,8 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
|
|||
flushClipboard();
|
||||
#endif
|
||||
|
||||
app.fakeDtor();
|
||||
|
||||
_exit(0);
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -3,8 +3,13 @@
|
|||
class QApplication;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Args;
|
||||
class Paths;
|
||||
class Settings;
|
||||
class Updates;
|
||||
|
||||
void runGui(QApplication &a, const Paths &paths, Settings &settings,
|
||||
const Args &args, Updates &updates);
|
||||
|
||||
void runGui(QApplication &a, Paths &paths, Settings &settings);
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "Args.hpp"
|
||||
#include "common/Args.hpp"
|
||||
|
||||
#include "common/QLogging.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
|
@ -66,7 +66,7 @@ QStringList extractCommandLine(
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
Args::Args(const QApplication &app)
|
||||
Args::Args(const QApplication &app, const Paths &paths)
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Chatterino 2 Client for Twitch Chat");
|
||||
|
@ -132,7 +132,7 @@ Args::Args(const QApplication &app)
|
|||
|
||||
if (parser.isSet(channelLayout))
|
||||
{
|
||||
this->applyCustomChannelLayout(parser.value(channelLayout));
|
||||
this->applyCustomChannelLayout(parser.value(channelLayout), paths);
|
||||
}
|
||||
|
||||
this->verbose = parser.isSet(verboseOption);
|
||||
|
@ -175,7 +175,7 @@ QStringList Args::currentArguments() const
|
|||
return this->currentArguments_;
|
||||
}
|
||||
|
||||
void Args::applyCustomChannelLayout(const QString &argValue)
|
||||
void Args::applyCustomChannelLayout(const QString &argValue, const Paths &paths)
|
||||
{
|
||||
WindowLayout layout;
|
||||
WindowDescriptor window;
|
||||
|
@ -187,10 +187,9 @@ void Args::applyCustomChannelLayout(const QString &argValue)
|
|||
window.type_ = WindowType::Main;
|
||||
|
||||
// Load main window layout from config file so we can use the same geometry
|
||||
const QRect configMainLayout = [] {
|
||||
const QString windowLayoutFile =
|
||||
combinePath(getPaths()->settingsDirectory,
|
||||
WindowManager::WINDOW_LAYOUT_FILENAME);
|
||||
const QRect configMainLayout = [paths] {
|
||||
const QString windowLayoutFile = combinePath(
|
||||
paths.settingsDirectory, WindowManager::WINDOW_LAYOUT_FILENAME);
|
||||
|
||||
const WindowLayout configLayout =
|
||||
WindowLayout::loadFromFile(windowLayoutFile);
|
||||
|
@ -198,7 +197,9 @@ void Args::applyCustomChannelLayout(const QString &argValue)
|
|||
for (const WindowDescriptor &window : configLayout.windows_)
|
||||
{
|
||||
if (window.type_ != WindowType::Main)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return window.geometry_;
|
||||
}
|
||||
|
@ -212,7 +213,9 @@ void Args::applyCustomChannelLayout(const QString &argValue)
|
|||
for (const QString &channelArg : channelArgList)
|
||||
{
|
||||
if (channelArg.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Twitch is default platform
|
||||
QString platform = "t";
|
||||
|
@ -248,18 +251,4 @@ void Args::applyCustomChannelLayout(const QString &argValue)
|
|||
}
|
||||
}
|
||||
|
||||
static Args *instance = nullptr;
|
||||
|
||||
void initArgs(const QApplication &app)
|
||||
{
|
||||
instance = new Args(app);
|
||||
}
|
||||
|
||||
const Args &getArgs()
|
||||
{
|
||||
assert(instance);
|
||||
|
||||
return *instance;
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
class Paths;
|
||||
|
||||
/// Command line arguments passed to Chatterino.
|
||||
///
|
||||
/// All accepted arguments:
|
||||
|
@ -30,7 +32,8 @@ namespace chatterino {
|
|||
class Args
|
||||
{
|
||||
public:
|
||||
Args(const QApplication &app);
|
||||
Args() = default;
|
||||
Args(const QApplication &app, const Paths &paths);
|
||||
|
||||
bool printVersion{};
|
||||
|
||||
|
@ -55,12 +58,9 @@ public:
|
|||
QStringList currentArguments() const;
|
||||
|
||||
private:
|
||||
void applyCustomChannelLayout(const QString &argValue);
|
||||
void applyCustomChannelLayout(const QString &argValue, const Paths &paths);
|
||||
|
||||
QStringList currentArguments_;
|
||||
};
|
||||
|
||||
void initArgs(const QApplication &app);
|
||||
const Args &getArgs();
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -82,7 +82,7 @@ LimitedQueueSnapshot<MessagePtr> Channel::getMessageSnapshot()
|
|||
void Channel::addMessage(MessagePtr message,
|
||||
std::optional<MessageFlags> overridingFlags)
|
||||
{
|
||||
auto app = getApp();
|
||||
auto *app = getApp();
|
||||
MessagePtr deleted;
|
||||
|
||||
if (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog))
|
||||
|
@ -101,7 +101,8 @@ void Channel::addMessage(MessagePtr message,
|
|||
{
|
||||
channelPlatform = "twitch";
|
||||
}
|
||||
app->logging->addMessage(this->name_, message, channelPlatform);
|
||||
getIApp()->getChatLogger()->addMessage(this->name_, message,
|
||||
channelPlatform);
|
||||
}
|
||||
|
||||
if (this->messages_.pushBack(message, deleted))
|
||||
|
@ -134,7 +135,7 @@ void Channel::disableAllMessages()
|
|||
int snapshotLength = snapshot.size();
|
||||
for (int i = 0; i < snapshotLength; i++)
|
||||
{
|
||||
auto &message = snapshot[i];
|
||||
const auto &message = snapshot[i];
|
||||
if (message->flags.hasAny({MessageFlag::System, MessageFlag::Timeout,
|
||||
MessageFlag::Whisper}))
|
||||
{
|
||||
|
@ -178,7 +179,7 @@ void Channel::fillInMissingMessages(const std::vector<MessagePtr> &messages)
|
|||
existingMessageIds.reserve(snapshot.size());
|
||||
|
||||
// First, collect the ids of every message already present in the channel
|
||||
for (auto &msg : snapshot)
|
||||
for (const auto &msg : snapshot)
|
||||
{
|
||||
if (msg->flags.has(MessageFlag::System) || msg->id.isEmpty())
|
||||
{
|
||||
|
@ -195,7 +196,7 @@ void Channel::fillInMissingMessages(const std::vector<MessagePtr> &messages)
|
|||
// being able to insert just-loaded historical messages at the end
|
||||
// in the correct place.
|
||||
auto lastMsg = snapshot[snapshot.size() - 1];
|
||||
for (auto &msg : messages)
|
||||
for (const auto &msg : messages)
|
||||
{
|
||||
// check if message already exists
|
||||
if (existingMessageIds.count(msg->id) != 0)
|
||||
|
@ -207,7 +208,7 @@ void Channel::fillInMissingMessages(const std::vector<MessagePtr> &messages)
|
|||
anyInserted = true;
|
||||
|
||||
bool insertedFlag = false;
|
||||
for (auto &snapshotMsg : snapshot)
|
||||
for (const auto &snapshotMsg : snapshot)
|
||||
{
|
||||
if (snapshotMsg->flags.has(MessageFlag::System))
|
||||
{
|
||||
|
@ -315,7 +316,6 @@ bool Channel::isBroadcaster() const
|
|||
|
||||
bool Channel::hasModRights() const
|
||||
{
|
||||
// fourtf: check if staff
|
||||
return this->isMod() || this->isBroadcaster();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,12 +27,16 @@ void ChatterSet::updateOnlineChatters(
|
|||
for (auto &&chatter : lowerCaseUsernames)
|
||||
{
|
||||
if (this->items.exists(chatter))
|
||||
{
|
||||
tmp.put(chatter, this->items.get(chatter));
|
||||
|
||||
// Less chatters than the limit => try to preserve as many as possible.
|
||||
}
|
||||
else if (lowerCaseUsernames.size() < chatterLimit)
|
||||
{
|
||||
tmp.put(chatter, chatter);
|
||||
}
|
||||
}
|
||||
|
||||
this->items = std::move(tmp);
|
||||
}
|
||||
|
@ -50,8 +54,10 @@ std::vector<QString> ChatterSet::filterByPrefix(const QString &prefix) const
|
|||
for (auto &&item : this->items)
|
||||
{
|
||||
if (item.first.startsWith(lowerPrefix))
|
||||
{
|
||||
result.push_back(item.second);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "common/Credentials.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
@ -40,7 +41,7 @@ bool useKeyring()
|
|||
#ifdef NO_QTKEYCHAIN
|
||||
return false;
|
||||
#endif
|
||||
if (getPaths()->isPortable())
|
||||
if (getIApp()->getPaths().isPortable())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -55,7 +56,8 @@ bool useKeyring()
|
|||
// Insecure storage:
|
||||
QString insecurePath()
|
||||
{
|
||||
return combinePath(getPaths()->settingsDirectory, "credentials.json");
|
||||
return combinePath(getIApp()->getPaths().settingsDirectory,
|
||||
"credentials.json");
|
||||
}
|
||||
|
||||
QJsonDocument loadInsecure()
|
||||
|
|
|
@ -56,10 +56,14 @@ public:
|
|||
void set(T flag, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
this->set(flag);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->unset(flag);
|
||||
}
|
||||
}
|
||||
|
||||
bool has(T flag) const
|
||||
{
|
||||
|
|
|
@ -1,413 +0,0 @@
|
|||
#include "common/NetworkPrivate.hpp"
|
||||
|
||||
#include "common/NetworkManager.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NetworkData::NetworkData()
|
||||
: lifetimeManager_(new QObject)
|
||||
{
|
||||
DebugCount::increase("NetworkData");
|
||||
}
|
||||
|
||||
NetworkData::~NetworkData()
|
||||
{
|
||||
this->lifetimeManager_->deleteLater();
|
||||
|
||||
DebugCount::decrease("NetworkData");
|
||||
}
|
||||
|
||||
QString NetworkData::getHash()
|
||||
{
|
||||
static std::mutex mu;
|
||||
|
||||
std::lock_guard lock(mu);
|
||||
|
||||
if (this->hash_.isEmpty())
|
||||
{
|
||||
QByteArray bytes;
|
||||
|
||||
bytes.append(this->request_.url().toString().toUtf8());
|
||||
|
||||
for (const auto &header : this->request_.rawHeaderList())
|
||||
{
|
||||
bytes.append(header);
|
||||
}
|
||||
|
||||
QByteArray hashBytes(
|
||||
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
|
||||
|
||||
this->hash_ = hashBytes.toHex();
|
||||
}
|
||||
|
||||
return this->hash_;
|
||||
}
|
||||
|
||||
void writeToCache(const std::shared_ptr<NetworkData> &data,
|
||||
const QByteArray &bytes)
|
||||
{
|
||||
if (data->cache_)
|
||||
{
|
||||
QtConcurrent::run([data, bytes] {
|
||||
QFile cachedFile(getPaths()->cacheDirectory() + "/" +
|
||||
data->getHash());
|
||||
|
||||
if (cachedFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
cachedFile.write(bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void loadUncached(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
DebugCount::increase("http request started");
|
||||
|
||||
NetworkRequester requester;
|
||||
auto *worker = new NetworkWorker;
|
||||
|
||||
worker->moveToThread(&NetworkManager::workerThread);
|
||||
|
||||
auto onUrlRequested = [data, worker]() mutable {
|
||||
if (data->hasTimeout_)
|
||||
{
|
||||
data->timer_ = new QTimer();
|
||||
data->timer_->setSingleShot(true);
|
||||
data->timer_->start(data->timeoutMS_);
|
||||
}
|
||||
|
||||
auto *reply = [&]() -> QNetworkReply * {
|
||||
switch (data->requestType_)
|
||||
{
|
||||
case NetworkRequestType::Get:
|
||||
return NetworkManager::accessManager.get(data->request_);
|
||||
|
||||
case NetworkRequestType::Put:
|
||||
return NetworkManager::accessManager.put(data->request_,
|
||||
data->payload_);
|
||||
|
||||
case NetworkRequestType::Delete:
|
||||
return NetworkManager::accessManager.deleteResource(
|
||||
data->request_);
|
||||
|
||||
case NetworkRequestType::Post:
|
||||
if (data->multiPartPayload_)
|
||||
{
|
||||
assert(data->payload_.isNull());
|
||||
|
||||
return NetworkManager::accessManager.post(
|
||||
data->request_, data->multiPartPayload_);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NetworkManager::accessManager.post(
|
||||
data->request_, data->payload_);
|
||||
}
|
||||
case NetworkRequestType::Patch:
|
||||
if (data->multiPartPayload_)
|
||||
{
|
||||
assert(data->payload_.isNull());
|
||||
|
||||
return NetworkManager::accessManager.sendCustomRequest(
|
||||
data->request_, "PATCH", data->multiPartPayload_);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NetworkManager::accessManager.sendCustomRequest(
|
||||
data->request_, "PATCH", data->payload_);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (reply == nullptr)
|
||||
{
|
||||
qCDebug(chatterinoCommon) << "Unhandled request type";
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->timer_ != nullptr && data->timer_->isActive())
|
||||
{
|
||||
QObject::connect(
|
||||
data->timer_, &QTimer::timeout, worker, [reply, data]() {
|
||||
qCDebug(chatterinoCommon) << "Aborted!";
|
||||
reply->abort();
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 [timed out] %2")
|
||||
.arg(networkRequestTypes.at(
|
||||
int(data->requestType_)),
|
||||
data->request_.url().toString());
|
||||
|
||||
if (data->onError_)
|
||||
{
|
||||
postToThread([data] {
|
||||
data->onError_(NetworkResult(
|
||||
NetworkResult::NetworkError::TimeoutError, {},
|
||||
{}));
|
||||
});
|
||||
}
|
||||
|
||||
if (data->finally_)
|
||||
{
|
||||
postToThread([data] {
|
||||
data->finally_();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data->onReplyCreated_)
|
||||
{
|
||||
data->onReplyCreated_(reply);
|
||||
}
|
||||
|
||||
auto handleReply = [data, reply]() mutable {
|
||||
if (data->hasCaller_ && data->caller_.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(pajlada): A reply was received, kill the timeout timer
|
||||
if (reply->error() != QNetworkReply::NetworkError::NoError)
|
||||
{
|
||||
if (reply->error() ==
|
||||
QNetworkReply::NetworkError::OperationCanceledError)
|
||||
{
|
||||
// Operation cancelled, most likely timed out
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 [cancelled] %2")
|
||||
.arg(networkRequestTypes.at(
|
||||
int(data->requestType_)),
|
||||
data->request_.url().toString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (data->onError_)
|
||||
{
|
||||
auto status = reply->attribute(
|
||||
QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (data->requestType_ == NetworkRequestType::Get)
|
||||
{
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 %2 %3")
|
||||
.arg(networkRequestTypes.at(
|
||||
int(data->requestType_)),
|
||||
QString::number(status.toInt()),
|
||||
data->request_.url().toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 %2 %3 %4")
|
||||
.arg(networkRequestTypes.at(
|
||||
int(data->requestType_)),
|
||||
QString::number(status.toInt()),
|
||||
data->request_.url().toString(),
|
||||
QString(data->payload_));
|
||||
}
|
||||
// TODO: Should this always be run on the GUI thread?
|
||||
postToThread([data, status, reply] {
|
||||
data->onError_(NetworkResult(reply->error(), status,
|
||||
reply->readAll()));
|
||||
});
|
||||
}
|
||||
|
||||
if (data->finally_)
|
||||
{
|
||||
postToThread([data] {
|
||||
data->finally_();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bytes = reply->readAll();
|
||||
writeToCache(data, bytes);
|
||||
|
||||
auto status =
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
|
||||
NetworkResult result(reply->error(), status, bytes);
|
||||
|
||||
DebugCount::increase("http request success");
|
||||
// log("starting {}", data->request_.url().toString());
|
||||
if (data->onSuccess_)
|
||||
{
|
||||
if (data->executeConcurrently_)
|
||||
{
|
||||
QtConcurrent::run([onSuccess = std::move(data->onSuccess_),
|
||||
result = std::move(result)] {
|
||||
onSuccess(result);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
data->onSuccess_(result);
|
||||
}
|
||||
}
|
||||
// log("finished {}", data->request_.url().toString());
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (data->requestType_ == NetworkRequestType::Get)
|
||||
{
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 %2 %3")
|
||||
.arg(networkRequestTypes.at(int(data->requestType_)),
|
||||
QString::number(status.toInt()),
|
||||
data->request_.url().toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 %3 %2 %4")
|
||||
.arg(networkRequestTypes.at(int(data->requestType_)),
|
||||
data->request_.url().toString(),
|
||||
QString::number(status.toInt()),
|
||||
QString(data->payload_));
|
||||
}
|
||||
if (data->finally_)
|
||||
{
|
||||
if (data->executeConcurrently_)
|
||||
{
|
||||
QtConcurrent::run([finally = std::move(data->finally_)] {
|
||||
finally();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
data->finally_();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (data->timer_ != nullptr)
|
||||
{
|
||||
QObject::connect(reply, &QNetworkReply::finished, data->timer_,
|
||||
&QObject::deleteLater);
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
reply, &QNetworkReply::finished, worker,
|
||||
[data, handleReply, worker]() mutable {
|
||||
if (data->executeConcurrently_ || isGuiThread())
|
||||
{
|
||||
handleReply();
|
||||
delete worker;
|
||||
}
|
||||
else
|
||||
{
|
||||
postToThread(
|
||||
[worker, cb = std::move(handleReply)]() mutable {
|
||||
cb();
|
||||
delete worker;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
||||
onUrlRequested);
|
||||
|
||||
emit requester.requestUrl();
|
||||
}
|
||||
|
||||
// First tried to load cached, then uncached.
|
||||
void loadCached(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash());
|
||||
|
||||
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
// File didn't exist OR File could not be opened
|
||||
loadUncached(std::move(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: check if bytes is empty?
|
||||
QByteArray bytes = cachedFile.readAll();
|
||||
NetworkResult result(NetworkResult::NetworkError::NoError, QVariant(200),
|
||||
bytes);
|
||||
|
||||
qCDebug(chatterinoHTTP)
|
||||
<< QString("%1 [CACHED] 200 %2")
|
||||
.arg(networkRequestTypes.at(int(data->requestType_)),
|
||||
data->request_.url().toString());
|
||||
if (data->onSuccess_)
|
||||
{
|
||||
if (data->executeConcurrently_ || isGuiThread())
|
||||
{
|
||||
// XXX: If outcome is Failure, we should invalidate the cache file
|
||||
// somehow/somewhere
|
||||
/*auto outcome =*/
|
||||
if (data->hasCaller_ && data->caller_.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
data->onSuccess_(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
postToThread([data, result]() {
|
||||
if (data->hasCaller_ && data->caller_.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data->onSuccess_(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data->finally_)
|
||||
{
|
||||
if (data->executeConcurrently_ || isGuiThread())
|
||||
{
|
||||
if (data->hasCaller_ && data->caller_.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data->finally_();
|
||||
}
|
||||
else
|
||||
{
|
||||
postToThread([data]() {
|
||||
if (data->hasCaller_ && data->caller_.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data->finally_();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void load(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
if (data->cache_)
|
||||
{
|
||||
QtConcurrent::run([data = std::move(data)]() mutable {
|
||||
loadCached(std::move(data));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
loadUncached(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,73 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/NetworkCommon.hpp"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NetworkResult;
|
||||
|
||||
class NetworkRequester : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void requestUrl();
|
||||
};
|
||||
|
||||
class NetworkWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void doneUrl();
|
||||
};
|
||||
|
||||
struct NetworkData {
|
||||
NetworkData();
|
||||
~NetworkData();
|
||||
|
||||
QNetworkRequest request_;
|
||||
bool hasCaller_{};
|
||||
QPointer<QObject> caller_;
|
||||
bool cache_{};
|
||||
bool executeConcurrently_{};
|
||||
|
||||
NetworkReplyCreatedCallback onReplyCreated_;
|
||||
NetworkErrorCallback onError_;
|
||||
NetworkSuccessCallback onSuccess_;
|
||||
NetworkFinallyCallback finally_;
|
||||
|
||||
NetworkRequestType requestType_ = NetworkRequestType::Get;
|
||||
|
||||
QByteArray payload_;
|
||||
// lifetime secured by lifetimeManager_
|
||||
QHttpMultiPart *multiPartPayload_{};
|
||||
|
||||
// Timer that tracks the timeout
|
||||
// By default, there's no explicit timeout for the request
|
||||
// to enable the timer, the "setTimeout" function needs to be called before
|
||||
// execute is called
|
||||
bool hasTimeout_{};
|
||||
int timeoutMS_{};
|
||||
QTimer *timer_ = nullptr;
|
||||
QObject *lifetimeManager_;
|
||||
|
||||
QString getHash();
|
||||
|
||||
private:
|
||||
QString hash_;
|
||||
};
|
||||
|
||||
void load(std::shared_ptr<NetworkData> &&data);
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,73 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class NullablePtr
|
||||
{
|
||||
public:
|
||||
NullablePtr()
|
||||
: element_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
NullablePtr(T *element)
|
||||
: element_(element)
|
||||
{
|
||||
}
|
||||
|
||||
T *operator->() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return element_;
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return *element_;
|
||||
}
|
||||
|
||||
T *get() const
|
||||
{
|
||||
assert(this->hasElement());
|
||||
|
||||
return this->element_;
|
||||
}
|
||||
|
||||
bool isNull() const
|
||||
{
|
||||
return this->element_ == nullptr;
|
||||
}
|
||||
|
||||
bool hasElement() const
|
||||
{
|
||||
return this->element_ != nullptr;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->hasElement();
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !this->hasElement();
|
||||
}
|
||||
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
operator NullablePtr<const T>() const
|
||||
{
|
||||
return NullablePtr<const T>(this->element_);
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
|
@ -308,10 +308,12 @@ public:
|
|||
for (auto &&x : list)
|
||||
{
|
||||
if (x.row() != list.first().row())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto data = new QMimeData;
|
||||
auto *data = new QMimeData;
|
||||
data->setData("chatterino_row_id", QByteArray::number(list[0].row()));
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public:
|
|||
Singleton(Singleton &&) = delete;
|
||||
Singleton &operator=(Singleton &&) = delete;
|
||||
|
||||
virtual void initialize(Settings &settings, Paths &paths)
|
||||
virtual void initialize(Settings &settings, const Paths &paths)
|
||||
{
|
||||
(void)(settings);
|
||||
(void)(paths);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "common/NetworkCommon.hpp"
|
||||
#include "common/network/NetworkCommon.hpp"
|
||||
|
||||
#include <QStringList>
|
||||
|
|
@ -13,7 +13,6 @@ class NetworkResult;
|
|||
|
||||
using NetworkSuccessCallback = std::function<void(NetworkResult)>;
|
||||
using NetworkErrorCallback = std::function<void(NetworkResult)>;
|
||||
using NetworkReplyCreatedCallback = std::function<void(QNetworkReply *)>;
|
||||
using NetworkFinallyCallback = std::function<void()>;
|
||||
|
||||
enum class NetworkRequestType {
|
||||
|
@ -23,13 +22,6 @@ enum class NetworkRequestType {
|
|||
Delete,
|
||||
Patch,
|
||||
};
|
||||
const static std::vector<QString> networkRequestTypes{
|
||||
"GET", //
|
||||
"POST", //
|
||||
"PUT", //
|
||||
"DELETE", //
|
||||
"PATCH", //
|
||||
};
|
||||
|
||||
// parseHeaderList takes a list of headers in string form,
|
||||
// where each header pair is separated by semicolons (;) and the header name and value is divided by a colon (:)
|
|
@ -1,4 +1,4 @@
|
|||
#include "common/NetworkManager.hpp"
|
||||
#include "common/network/NetworkManager.hpp"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
205
src/common/network/NetworkPrivate.cpp
Normal file
205
src/common/network/NetworkPrivate.cpp
Normal file
|
@ -0,0 +1,205 @@
|
|||
#include "common/network/NetworkPrivate.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/network/NetworkManager.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "common/network/NetworkTask.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/AbandonObject.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
#include <QCryptographicHash>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#ifdef NDEBUG
|
||||
constexpr qsizetype SLOW_HTTP_THRESHOLD = 30;
|
||||
#else
|
||||
constexpr qsizetype SLOW_HTTP_THRESHOLD = 90;
|
||||
#endif
|
||||
|
||||
using namespace chatterino::network::detail;
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace chatterino;
|
||||
|
||||
void runCallback(bool concurrent, auto &&fn)
|
||||
{
|
||||
if (concurrent)
|
||||
{
|
||||
std::ignore = QtConcurrent::run(std::forward<decltype(fn)>(fn));
|
||||
}
|
||||
else
|
||||
{
|
||||
runInGuiThread(std::forward<decltype(fn)>(fn));
|
||||
}
|
||||
}
|
||||
|
||||
void loadUncached(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
DebugCount::increase("http request started");
|
||||
|
||||
NetworkRequester requester;
|
||||
auto *worker = new NetworkTask(std::move(data));
|
||||
|
||||
worker->moveToThread(&NetworkManager::workerThread);
|
||||
|
||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
||||
&NetworkTask::run);
|
||||
|
||||
emit requester.requestUrl();
|
||||
}
|
||||
|
||||
void loadCached(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
QFile cachedFile(getIApp()->getPaths().cacheDirectory() + "/" +
|
||||
data->getHash());
|
||||
|
||||
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
loadUncached(std::move(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX: check if bytes is empty?
|
||||
QByteArray bytes = cachedFile.readAll();
|
||||
|
||||
qCDebug(chatterinoHTTP).noquote() << data->typeString() << "[CACHED] 200"
|
||||
<< data->request.url().toString();
|
||||
|
||||
data->emitSuccess(
|
||||
{NetworkResult::NetworkError::NoError, QVariant(200), bytes});
|
||||
data->emitFinally();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
NetworkData::NetworkData()
|
||||
{
|
||||
DebugCount::increase("NetworkData");
|
||||
}
|
||||
|
||||
NetworkData::~NetworkData()
|
||||
{
|
||||
DebugCount::decrease("NetworkData");
|
||||
}
|
||||
|
||||
QString NetworkData::getHash()
|
||||
{
|
||||
if (this->hash_.isEmpty())
|
||||
{
|
||||
QByteArray bytes;
|
||||
|
||||
bytes.append(this->request.url().toString().toUtf8());
|
||||
|
||||
for (const auto &header : this->request.rawHeaderList())
|
||||
{
|
||||
bytes.append(header);
|
||||
}
|
||||
|
||||
QByteArray hashBytes(
|
||||
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
|
||||
|
||||
this->hash_ = hashBytes.toHex();
|
||||
}
|
||||
|
||||
return this->hash_;
|
||||
}
|
||||
|
||||
void NetworkData::emitSuccess(NetworkResult &&result)
|
||||
{
|
||||
if (!this->onSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runCallback(this->executeConcurrently,
|
||||
[cb = std::move(this->onSuccess), result = std::move(result),
|
||||
url = this->request.url(), hasCaller = this->hasCaller,
|
||||
caller = this->caller]() {
|
||||
if (hasCaller && caller.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
cb(result);
|
||||
if (timer.elapsed() > SLOW_HTTP_THRESHOLD)
|
||||
{
|
||||
qCWarning(chatterinoHTTP)
|
||||
<< "Slow HTTP success handler for" << url.toString()
|
||||
<< timer.elapsed()
|
||||
<< "ms (threshold:" << SLOW_HTTP_THRESHOLD << "ms)";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkData::emitError(NetworkResult &&result)
|
||||
{
|
||||
if (!this->onError)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runCallback(this->executeConcurrently,
|
||||
[cb = std::move(this->onError), result = std::move(result),
|
||||
hasCaller = this->hasCaller, caller = this->caller]() {
|
||||
if (hasCaller && caller.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cb(result);
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkData::emitFinally()
|
||||
{
|
||||
if (!this->finally)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
runCallback(this->executeConcurrently,
|
||||
[cb = std::move(this->finally), hasCaller = this->hasCaller,
|
||||
caller = this->caller]() {
|
||||
if (hasCaller && caller.isNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cb();
|
||||
});
|
||||
}
|
||||
|
||||
QLatin1String NetworkData::typeString() const
|
||||
{
|
||||
auto view = magic_enum::enum_name<NetworkRequestType>(this->requestType);
|
||||
return QLatin1String{view.data(),
|
||||
static_cast<QLatin1String::size_type>(view.size())};
|
||||
}
|
||||
|
||||
void load(std::shared_ptr<NetworkData> &&data)
|
||||
{
|
||||
if (data->cache)
|
||||
{
|
||||
std::ignore = QtConcurrent::run([data = std::move(data)]() mutable {
|
||||
loadCached(std::move(data));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
loadUncached(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
71
src/common/network/NetworkPrivate.hpp
Normal file
71
src/common/network/NetworkPrivate.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/network/NetworkCommon.hpp"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPointer>
|
||||
#include <QTimer>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NetworkResult;
|
||||
|
||||
class NetworkRequester : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void requestUrl();
|
||||
};
|
||||
|
||||
class NetworkData
|
||||
{
|
||||
public:
|
||||
NetworkData();
|
||||
~NetworkData();
|
||||
NetworkData(const NetworkData &) = delete;
|
||||
NetworkData(NetworkData &&) = delete;
|
||||
NetworkData &operator=(const NetworkData &) = delete;
|
||||
NetworkData &operator=(NetworkData &&) = delete;
|
||||
|
||||
QNetworkRequest request;
|
||||
bool hasCaller{};
|
||||
QPointer<QObject> caller;
|
||||
bool cache{};
|
||||
bool executeConcurrently{};
|
||||
|
||||
NetworkSuccessCallback onSuccess;
|
||||
NetworkErrorCallback onError;
|
||||
NetworkFinallyCallback finally;
|
||||
|
||||
NetworkRequestType requestType = NetworkRequestType::Get;
|
||||
|
||||
QByteArray payload;
|
||||
std::unique_ptr<QHttpMultiPart, DeleteLater> multiPartPayload;
|
||||
|
||||
/// By default, there's no explicit timeout for the request.
|
||||
/// To set a timeout, use NetworkRequest's timeout method
|
||||
std::optional<std::chrono::milliseconds> timeout{};
|
||||
|
||||
QString getHash();
|
||||
|
||||
void emitSuccess(NetworkResult &&result);
|
||||
void emitError(NetworkResult &&result);
|
||||
void emitFinally();
|
||||
|
||||
QLatin1String typeString() const;
|
||||
|
||||
private:
|
||||
QString hash_;
|
||||
};
|
||||
|
||||
void load(std::shared_ptr<NetworkData> &&data);
|
||||
|
||||
} // namespace chatterino
|
|
@ -1,6 +1,6 @@
|
|||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/network/NetworkRequest.hpp"
|
||||
|
||||
#include "common/NetworkPrivate.hpp"
|
||||
#include "common/network/NetworkPrivate.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "common/Version.hpp"
|
||||
|
||||
|
@ -16,8 +16,8 @@ NetworkRequest::NetworkRequest(const std::string &url,
|
|||
NetworkRequestType requestType)
|
||||
: data(new NetworkData)
|
||||
{
|
||||
this->data->request_.setUrl(QUrl(QString::fromStdString(url)));
|
||||
this->data->requestType_ = requestType;
|
||||
this->data->request.setUrl(QUrl(QString::fromStdString(url)));
|
||||
this->data->requestType = requestType;
|
||||
|
||||
this->initializeDefaultValues();
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ NetworkRequest::NetworkRequest(const std::string &url,
|
|||
NetworkRequest::NetworkRequest(const QUrl &url, NetworkRequestType requestType)
|
||||
: data(new NetworkData)
|
||||
{
|
||||
this->data->request_.setUrl(url);
|
||||
this->data->requestType_ = requestType;
|
||||
this->data->request.setUrl(url);
|
||||
this->data->requestType = requestType;
|
||||
|
||||
this->initializeDefaultValues();
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ NetworkRequest::~NetworkRequest() = default;
|
|||
|
||||
NetworkRequest NetworkRequest::type(NetworkRequestType newRequestType) &&
|
||||
{
|
||||
this->data->requestType_ = newRequestType;
|
||||
this->data->requestType = newRequestType;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
|
@ -46,61 +46,55 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
|
|||
// Caller must be in gui thread
|
||||
assert(caller->thread() == qApp->thread());
|
||||
|
||||
this->data->caller_ = const_cast<QObject *>(caller);
|
||||
this->data->hasCaller_ = true;
|
||||
this->data->caller = const_cast<QObject *>(caller);
|
||||
this->data->hasCaller = true;
|
||||
}
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::onReplyCreated(NetworkReplyCreatedCallback cb) &&
|
||||
{
|
||||
this->data->onReplyCreated_ = std::move(cb);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::onError(NetworkErrorCallback cb) &&
|
||||
{
|
||||
this->data->onError_ = std::move(cb);
|
||||
this->data->onError = std::move(cb);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) &&
|
||||
{
|
||||
this->data->onSuccess_ = std::move(cb);
|
||||
this->data->onSuccess = std::move(cb);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) &&
|
||||
{
|
||||
this->data->finally_ = std::move(cb);
|
||||
this->data->finally = std::move(cb);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::header(const char *headerName,
|
||||
const char *value) &&
|
||||
{
|
||||
this->data->request_.setRawHeader(headerName, value);
|
||||
this->data->request.setRawHeader(headerName, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::header(const char *headerName,
|
||||
const QByteArray &value) &&
|
||||
{
|
||||
this->data->request_.setRawHeader(headerName, value);
|
||||
this->data->request.setRawHeader(headerName, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::header(const char *headerName,
|
||||
const QString &value) &&
|
||||
{
|
||||
this->data->request_.setRawHeader(headerName, value.toUtf8());
|
||||
this->data->request.setRawHeader(headerName, value.toUtf8());
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::header(QNetworkRequest::KnownHeaders header,
|
||||
const QVariant &value) &&
|
||||
{
|
||||
this->data->request_.setHeader(header, value);
|
||||
this->data->request.setHeader(header, value);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
|
@ -109,28 +103,26 @@ NetworkRequest NetworkRequest::headerList(
|
|||
{
|
||||
for (const auto &[headerName, headerValue] : headers)
|
||||
{
|
||||
this->data->request_.setRawHeader(headerName, headerValue);
|
||||
this->data->request.setRawHeader(headerName, headerValue);
|
||||
}
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::timeout(int ms) &&
|
||||
{
|
||||
this->data->hasTimeout_ = true;
|
||||
this->data->timeoutMS_ = ms;
|
||||
this->data->timeout = std::chrono::milliseconds(ms);
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::concurrent() &&
|
||||
{
|
||||
this->data->executeConcurrently_ = true;
|
||||
this->data->executeConcurrently = true;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::multiPart(QHttpMultiPart *payload) &&
|
||||
{
|
||||
payload->setParent(this->data->lifetimeManager_);
|
||||
this->data->multiPartPayload_ = payload;
|
||||
this->data->multiPartPayload = {payload, {}};
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
|
@ -138,13 +130,13 @@ NetworkRequest NetworkRequest::followRedirects(bool on) &&
|
|||
{
|
||||
if (on)
|
||||
{
|
||||
this->data->request_.setAttribute(
|
||||
this->data->request.setAttribute(
|
||||
QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->data->request_.setAttribute(
|
||||
this->data->request.setAttribute(
|
||||
QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::ManualRedirectPolicy);
|
||||
}
|
||||
|
@ -154,13 +146,13 @@ NetworkRequest NetworkRequest::followRedirects(bool on) &&
|
|||
|
||||
NetworkRequest NetworkRequest::payload(const QByteArray &payload) &&
|
||||
{
|
||||
this->data->payload_ = payload;
|
||||
this->data->payload = payload;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::cache() &&
|
||||
{
|
||||
this->data->cache_ = true;
|
||||
this->data->cache = true;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
|
@ -169,15 +161,14 @@ void NetworkRequest::execute()
|
|||
this->executed_ = true;
|
||||
|
||||
// Only allow caching for GET request
|
||||
if (this->data->cache_ &&
|
||||
this->data->requestType_ != NetworkRequestType::Get)
|
||||
if (this->data->cache && this->data->requestType != NetworkRequestType::Get)
|
||||
{
|
||||
qCDebug(chatterinoCommon) << "Can only cache GET requests!";
|
||||
this->data->cache_ = false;
|
||||
this->data->cache = false;
|
||||
}
|
||||
|
||||
// Can not have a caller and be concurrent at the same time.
|
||||
assert(!(this->data->caller_ && this->data->executeConcurrently_));
|
||||
assert(!(this->data->caller && this->data->executeConcurrently));
|
||||
|
||||
load(std::move(this->data));
|
||||
}
|
||||
|
@ -189,7 +180,7 @@ void NetworkRequest::initializeDefaultValues()
|
|||
Version::instance().commitHash())
|
||||
.toUtf8();
|
||||
|
||||
this->data->request_.setRawHeader("User-Agent", userAgent);
|
||||
this->data->request.setRawHeader("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
NetworkRequest NetworkRequest::json(const QJsonArray &root) &&
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/NetworkCommon.hpp"
|
||||
#include "common/network/NetworkCommon.hpp"
|
||||
|
||||
#include <QHttpMultiPart>
|
||||
|
||||
|
@ -12,7 +12,7 @@ class QJsonDocument;
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
struct NetworkData;
|
||||
class NetworkData;
|
||||
|
||||
class NetworkRequest final
|
||||
{
|
||||
|
@ -43,7 +43,6 @@ public:
|
|||
|
||||
NetworkRequest type(NetworkRequestType newRequestType) &&;
|
||||
|
||||
NetworkRequest onReplyCreated(NetworkReplyCreatedCallback cb) &&;
|
||||
NetworkRequest onError(NetworkErrorCallback cb) &&;
|
||||
NetworkRequest onSuccess(NetworkSuccessCallback cb) &&;
|
||||
NetworkRequest finally(NetworkFinallyCallback cb) &&;
|
|
@ -1,4 +1,4 @@
|
|||
#include "common/NetworkResult.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
|
||||
#include "common/QLogging.hpp"
|
||||
|
191
src/common/network/NetworkTask.cpp
Normal file
191
src/common/network/NetworkTask.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include "common/network/NetworkTask.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/network/NetworkManager.hpp"
|
||||
#include "common/network/NetworkPrivate.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/AbandonObject.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace chatterino::network::detail {
|
||||
|
||||
NetworkTask::NetworkTask(std::shared_ptr<NetworkData> &&data)
|
||||
: data_(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
NetworkTask::~NetworkTask()
|
||||
{
|
||||
if (this->reply_)
|
||||
{
|
||||
this->reply_->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTask::run()
|
||||
{
|
||||
const auto &timeout = this->data_->timeout;
|
||||
if (timeout.has_value())
|
||||
{
|
||||
this->timer_ = new QTimer(this);
|
||||
this->timer_->setSingleShot(true);
|
||||
this->timer_->start(timeout.value());
|
||||
QObject::connect(this->timer_, &QTimer::timeout, this,
|
||||
&NetworkTask::timeout);
|
||||
}
|
||||
|
||||
this->reply_ = this->createReply();
|
||||
if (!this->reply_)
|
||||
{
|
||||
this->deleteLater();
|
||||
return;
|
||||
}
|
||||
QObject::connect(this->reply_, &QNetworkReply::finished, this,
|
||||
&NetworkTask::finished);
|
||||
}
|
||||
|
||||
QNetworkReply *NetworkTask::createReply()
|
||||
{
|
||||
const auto &data = this->data_;
|
||||
const auto &request = this->data_->request;
|
||||
auto &accessManager = NetworkManager::accessManager;
|
||||
switch (this->data_->requestType)
|
||||
{
|
||||
case NetworkRequestType::Get:
|
||||
return accessManager.get(request);
|
||||
|
||||
case NetworkRequestType::Put:
|
||||
return accessManager.put(request, data->payload);
|
||||
|
||||
case NetworkRequestType::Delete:
|
||||
return accessManager.deleteResource(data->request);
|
||||
|
||||
case NetworkRequestType::Post:
|
||||
if (data->multiPartPayload)
|
||||
{
|
||||
assert(data->payload.isNull());
|
||||
|
||||
return accessManager.post(request,
|
||||
data->multiPartPayload.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return accessManager.post(request, data->payload);
|
||||
}
|
||||
case NetworkRequestType::Patch:
|
||||
if (data->multiPartPayload)
|
||||
{
|
||||
assert(data->payload.isNull());
|
||||
|
||||
return accessManager.sendCustomRequest(
|
||||
request, "PATCH", data->multiPartPayload.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return NetworkManager::accessManager.sendCustomRequest(
|
||||
request, "PATCH", data->payload);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NetworkTask::logReply()
|
||||
{
|
||||
auto status =
|
||||
this->reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute)
|
||||
.toInt();
|
||||
if (this->data_->requestType == NetworkRequestType::Get)
|
||||
{
|
||||
qCDebug(chatterinoHTTP).noquote()
|
||||
<< this->data_->typeString() << status
|
||||
<< this->data_->request.url().toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
qCDebug(chatterinoHTTP).noquote()
|
||||
<< this->data_->typeString()
|
||||
<< this->data_->request.url().toString() << status
|
||||
<< QString(this->data_->payload);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkTask::writeToCache(const QByteArray &bytes) const
|
||||
{
|
||||
std::ignore = QtConcurrent::run([data = this->data_, bytes] {
|
||||
QFile cachedFile(getIApp()->getPaths().cacheDirectory() + "/" +
|
||||
data->getHash());
|
||||
|
||||
if (cachedFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
cachedFile.write(bytes);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkTask::timeout()
|
||||
{
|
||||
AbandonObject guard(this);
|
||||
|
||||
// prevent abort() from calling finished()
|
||||
QObject::disconnect(this->reply_, &QNetworkReply::finished, this,
|
||||
&NetworkTask::finished);
|
||||
this->reply_->abort();
|
||||
|
||||
qCDebug(chatterinoHTTP).noquote()
|
||||
<< this->data_->typeString() << "[timed out]"
|
||||
<< this->data_->request.url().toString();
|
||||
|
||||
this->data_->emitError({NetworkResult::NetworkError::TimeoutError, {}, {}});
|
||||
this->data_->emitFinally();
|
||||
}
|
||||
|
||||
void NetworkTask::finished()
|
||||
{
|
||||
AbandonObject guard(this);
|
||||
|
||||
if (this->timer_)
|
||||
{
|
||||
this->timer_->stop();
|
||||
}
|
||||
|
||||
auto *reply = this->reply_;
|
||||
auto status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
|
||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
{
|
||||
// Operation cancelled, most likely timed out
|
||||
qCDebug(chatterinoHTTP).noquote()
|
||||
<< this->data_->typeString() << "[cancelled]"
|
||||
<< this->data_->request.url().toString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
this->logReply();
|
||||
this->data_->emitError({reply->error(), status, reply->readAll()});
|
||||
this->data_->emitFinally();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bytes = reply->readAll();
|
||||
|
||||
if (this->data_->cache)
|
||||
{
|
||||
this->writeToCache(bytes);
|
||||
}
|
||||
|
||||
DebugCount::increase("http request success");
|
||||
this->logReply();
|
||||
this->data_->emitSuccess({reply->error(), status, bytes});
|
||||
this->data_->emitFinally();
|
||||
}
|
||||
|
||||
} // namespace chatterino::network::detail
|
51
src/common/network/NetworkTask.hpp
Normal file
51
src/common/network/NetworkTask.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class NetworkData;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace chatterino::network::detail {
|
||||
|
||||
class NetworkTask : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkTask(std::shared_ptr<NetworkData> &&data);
|
||||
~NetworkTask() override;
|
||||
|
||||
NetworkTask(const NetworkTask &) = delete;
|
||||
NetworkTask(NetworkTask &&) = delete;
|
||||
NetworkTask &operator=(const NetworkTask &) = delete;
|
||||
NetworkTask &operator=(NetworkTask &&) = delete;
|
||||
|
||||
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
|
||||
public slots:
|
||||
void run();
|
||||
|
||||
private:
|
||||
QNetworkReply *createReply();
|
||||
|
||||
void logReply();
|
||||
void writeToCache(const QByteArray &bytes) const;
|
||||
|
||||
std::shared_ptr<NetworkData> data_;
|
||||
QNetworkReply *reply_{}; // parent: default (accessManager)
|
||||
QTimer *timer_{}; // parent: this
|
||||
|
||||
// NOLINTNEXTLINE(readability-redundant-access-specifiers)
|
||||
private slots:
|
||||
void timeout();
|
||||
void finished();
|
||||
};
|
||||
|
||||
} // namespace chatterino::network::detail
|
|
@ -22,7 +22,7 @@ AccountController::AccountController()
|
|||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||
if (args.caller != this)
|
||||
{
|
||||
auto &accs = this->twitch.accounts.raw();
|
||||
const auto &accs = this->twitch.accounts.raw();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
|
@ -47,7 +47,7 @@ AccountController::AccountController()
|
|||
});
|
||||
}
|
||||
|
||||
void AccountController::initialize(Settings &settings, Paths &paths)
|
||||
void AccountController::initialize(Settings &settings, const Paths &paths)
|
||||
{
|
||||
this->twitch.load();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public:
|
|||
|
||||
AccountModel *createModel(QObject *parent);
|
||||
|
||||
void initialize(Settings &settings, Paths &paths) override;
|
||||
void initialize(Settings &settings, const Paths &paths) override;
|
||||
|
||||
TwitchAccountManager twitch;
|
||||
|
||||
|
|
|
@ -261,7 +261,7 @@ const std::unordered_map<QString, VariableReplacer> COMMAND_VARS{
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
void CommandController::initialize(Settings &, Paths &paths)
|
||||
void CommandController::initialize(Settings &, const Paths &paths)
|
||||
{
|
||||
// Update commands map when the vector of commands has been updated
|
||||
auto addFirstMatchToMap = [this](auto args) {
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
bool dryRun);
|
||||
QStringList getDefaultChatterinoCommandList();
|
||||
|
||||
void initialize(Settings &, Paths &paths) override;
|
||||
void initialize(Settings &, const Paths &paths) override;
|
||||
void save() override;
|
||||
|
||||
CommandModel *createModel(QObject *parent);
|
||||
|
|
|
@ -66,7 +66,7 @@ QString uptime(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /uptime command only works in Twitch Channels"));
|
||||
"The /uptime command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -188,14 +188,14 @@ QString clip(const CommandContext &ctx)
|
|||
type != Channel::Type::Twitch && type != Channel::Type::TwitchWatching)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clip command only works in Twitch Channels"));
|
||||
"The /clip command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clip command only works in Twitch Channels"));
|
||||
"The /clip command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -214,7 +214,7 @@ QString marker(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /marker command only works in Twitch channels"));
|
||||
"The /marker command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -520,7 +520,7 @@ QString unstableSetUserClientSideColor(const CommandContext &ctx)
|
|||
{
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("The /unstable-set-user-color command only "
|
||||
"works in Twitch channels"));
|
||||
"works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
|
|
|
@ -23,7 +23,7 @@ QString addModerator(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /mod command only works in Twitch channels"));
|
||||
"The /mod command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
|
|
|
@ -23,7 +23,7 @@ QString addVIP(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /vip command only works in Twitch channels"));
|
||||
"The /vip command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
|
|
|
@ -37,7 +37,7 @@ QString sendAnnouncement(const CommandContext &ctx)
|
|||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /announce command"));
|
||||
"You must be logged in to use the /announce command."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ QString sendBan(const CommandContext &ctx)
|
|||
if (twitchChannel == nullptr)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("The /ban command only works in Twitch channels")));
|
||||
QString("The /ban command only works in Twitch channels.")));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ QString sendBanById(const CommandContext &ctx)
|
|||
if (twitchChannel == nullptr)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("The /banid command only works in Twitch channels")));
|
||||
QString("The /banid command only works in Twitch channels.")));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -241,7 +241,7 @@ QString sendTimeout(const CommandContext &ctx)
|
|||
if (twitchChannel == nullptr)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
QString("The /timeout command only works in Twitch channels")));
|
||||
QString("The /timeout command only works in Twitch channels.")));
|
||||
return "";
|
||||
}
|
||||
const auto *usageStr =
|
||||
|
|
|
@ -27,7 +27,7 @@ QString blockUser(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /block command only works in Twitch channels"));
|
||||
"The /block command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ QString unblockUser(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unblock command only works in Twitch channels"));
|
||||
"The /unblock command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ QString emoteOnly(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /emoteonly command only works in Twitch channels"));
|
||||
"The /emoteonly command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ QString emoteOnlyOff(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /emoteonlyoff command only works in Twitch channels"));
|
||||
"The /emoteonlyoff command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ QString subscribers(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /subscribers command only works in Twitch channels"));
|
||||
"The /subscribers command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -200,7 +200,7 @@ QString subscribersOff(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /subscribersoff command only works in Twitch channels"));
|
||||
"The /subscribersoff command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,7 @@ QString slow(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /slow command only works in Twitch channels"));
|
||||
"The /slow command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -277,7 +277,7 @@ QString slowOff(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /slowoff command only works in Twitch channels"));
|
||||
"The /slowoff command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@ QString followers(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /followers command only works in Twitch channels"));
|
||||
"The /followers command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -355,7 +355,7 @@ QString followersOff(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /followersoff command only works in Twitch channels"));
|
||||
"The /followersoff command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -385,7 +385,7 @@ QString uniqueChat(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /uniquechat command only works in Twitch channels"));
|
||||
"The /uniquechat command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -415,7 +415,7 @@ QString uniqueChatOff(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /uniquechatoff command only works in Twitch channels"));
|
||||
"The /uniquechatoff command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ QString chatters(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /chatters command only works in Twitch Channels"));
|
||||
"The /chatters command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ QString chatters(const CommandContext &ctx)
|
|||
getApp()->accounts->twitch.getCurrent()->getUserId(), 1,
|
||||
[channel{ctx.channel}](auto result) {
|
||||
channel->addMessage(
|
||||
makeSystemMessage(QString("Chatter count: %1")
|
||||
makeSystemMessage(QString("Chatter count: %1.")
|
||||
.arg(localizeNumbers(result.total))));
|
||||
},
|
||||
[channel{ctx.channel}](auto error, auto message) {
|
||||
|
@ -101,7 +101,7 @@ QString testChatters(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /test-chatters command only works in Twitch Channels"));
|
||||
"The /test-chatters command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
|
@ -102,7 +102,7 @@ QString deleteAllMessages(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /clear command only works in Twitch channels"));
|
||||
"The /clear command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ QString deleteOneMessage(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /delete command only works in Twitch channels"));
|
||||
"The /delete command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ QString getModerators(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /mods command only works in Twitch Channels"));
|
||||
"The /mods command only works in Twitch Channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ QString getVIPs(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /vips command only works in Twitch channels"));
|
||||
"The /vips command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ QString startRaid(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /raid command only works in Twitch channels"));
|
||||
"The /raid command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ QString cancelRaid(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unraid command only works in Twitch channels"));
|
||||
"The /unraid command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ QString removeModerator(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unmod command only works in Twitch channels"));
|
||||
"The /unmod command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
|
|
|
@ -23,7 +23,7 @@ QString removeVIP(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /unvip command only works in Twitch channels"));
|
||||
"The /unvip command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
if (ctx.words.size() < 2)
|
||||
|
|
|
@ -20,7 +20,7 @@ QString sendReply(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /reply command only works in Twitch channels"));
|
||||
"The /reply command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ QString sendReply(const CommandContext &ctx)
|
|||
}
|
||||
|
||||
ctx.channel->addMessage(
|
||||
makeSystemMessage("A message from that user wasn't found"));
|
||||
makeSystemMessage("A message from that user wasn't found."));
|
||||
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ QString toggleShieldMode(const CommandContext &ctx, bool isActivating)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QStringLiteral("The %1 command only works in Twitch channels")
|
||||
QStringLiteral("The %1 command only works in Twitch channels.")
|
||||
.arg(command)));
|
||||
return {};
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ QString toggleShieldMode(const CommandContext &ctx, bool isActivating)
|
|||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QStringLiteral("You must be logged in to use the %1 command")
|
||||
QStringLiteral("You must be logged in to use the %1 command.")
|
||||
.arg(command)));
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -15,12 +15,12 @@ QString sendShoutout(const CommandContext &ctx)
|
|||
{
|
||||
auto *twitchChannel = ctx.twitchChannel;
|
||||
auto channel = ctx.channel;
|
||||
auto words = &ctx.words;
|
||||
const auto *words = &ctx.words;
|
||||
|
||||
if (twitchChannel == nullptr)
|
||||
{
|
||||
channel->addMessage(makeSystemMessage(
|
||||
"The /shoutout command only works in Twitch channels"));
|
||||
"The /shoutout command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ QString sendShoutout(const CommandContext &ctx)
|
|||
if (currentUser->isAnon())
|
||||
{
|
||||
channel->addMessage(
|
||||
makeSystemMessage("You must be logged in to send shoutout"));
|
||||
makeSystemMessage("You must be logged in to send shoutout."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ QString startCommercial(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /commercial command only works in Twitch channels"));
|
||||
"The /commercial command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ QString startCommercial(const CommandContext &ctx)
|
|||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /commercial command"));
|
||||
"You must be logged in to use the /commercial command."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ QString unbanUser(const CommandContext &ctx)
|
|||
if (ctx.twitchChannel == nullptr)
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
QString("The %1 command only works in Twitch channels")
|
||||
QString("The %1 command only works in Twitch channels.")
|
||||
.arg(commandName)));
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "controllers/commands/builtin/twitch/UpdateChannel.hpp"
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "common/network/NetworkResult.hpp"
|
||||
#include "controllers/commands/CommandContext.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
|
|
|
@ -22,7 +22,7 @@ QString updateUserColor(const CommandContext &ctx)
|
|||
if (!ctx.channel->isTwitchChannel())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"The /color command only works in Twitch channels"));
|
||||
"The /color command only works in Twitch channels."));
|
||||
return "";
|
||||
}
|
||||
auto user = getApp()->accounts->twitch.getCurrent();
|
||||
|
@ -31,7 +31,7 @@ QString updateUserColor(const CommandContext &ctx)
|
|||
if (user->isAnon())
|
||||
{
|
||||
ctx.channel->addMessage(makeSystemMessage(
|
||||
"You must be logged in to use the /color command"));
|
||||
"You must be logged in to use the /color command."));
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ FilterSet::FilterSet(const QList<QUuid> &filterIds)
|
|||
for (const auto &f : *filters)
|
||||
{
|
||||
if (filterIds.contains(f->getId()))
|
||||
{
|
||||
this->filters_.insert(f->getId(), f);
|
||||
}
|
||||
}
|
||||
|
||||
this->listener_ =
|
||||
getSettings()->filterRecords.delayedItemsChanged.connect([this] {
|
||||
|
@ -36,14 +38,18 @@ FilterSet::~FilterSet()
|
|||
bool FilterSet::filter(const MessagePtr &m, ChannelPtr channel) const
|
||||
{
|
||||
if (this->filters_.size() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
filters::ContextMap context = filters::buildContextMap(m, channel.get());
|
||||
for (const auto &f : this->filters_.values())
|
||||
{
|
||||
if (!f->valid() || !f->filter(context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
* flags.whisper
|
||||
* flags.reply
|
||||
* flags.automod
|
||||
* flags.restricted
|
||||
* flags.monitored
|
||||
*
|
||||
* message.content
|
||||
* message.length
|
||||
|
@ -101,6 +103,8 @@ ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
|
|||
{"flags.whisper", m->flags.has(MessageFlag::Whisper)},
|
||||
{"flags.reply", m->flags.has(MessageFlag::ReplyMessage)},
|
||||
{"flags.automod", m->flags.has(MessageFlag::AutoMod)},
|
||||
{"flags.restricted", m->flags.has(MessageFlag::RestrictedMessage)},
|
||||
{"flags.monitored", m->flags.has(MessageFlag::MonitoredMessage)},
|
||||
|
||||
{"message.content", m->messageText},
|
||||
{"message.length", m->messageText.length()},
|
||||
|
|
|
@ -44,6 +44,8 @@ static const QMap<QString, Type> MESSAGE_TYPING_CONTEXT = {
|
|||
{"flags.whisper", Type::Bool},
|
||||
{"flags.reply", Type::Bool},
|
||||
{"flags.automod", Type::Bool},
|
||||
{"flags.restricted", Type::Bool},
|
||||
{"flags.monitored", Type::Bool},
|
||||
{"message.content", Type::String},
|
||||
{"message.length", Type::Int},
|
||||
};
|
||||
|
|
|
@ -105,7 +105,9 @@ QString Tokenizer::current() const
|
|||
QString Tokenizer::preview() const
|
||||
{
|
||||
if (this->hasNext())
|
||||
{
|
||||
return this->tokens_.at(this->i_);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -172,51 +174,97 @@ const QStringList Tokenizer::allTokens()
|
|||
TokenType Tokenizer::tokenize(const QString &text)
|
||||
{
|
||||
if (text == "&&")
|
||||
{
|
||||
return TokenType::AND;
|
||||
}
|
||||
else if (text == "||")
|
||||
{
|
||||
return TokenType::OR;
|
||||
}
|
||||
else if (text == "(")
|
||||
{
|
||||
return TokenType::LP;
|
||||
}
|
||||
else if (text == ")")
|
||||
{
|
||||
return TokenType::RP;
|
||||
}
|
||||
else if (text == "{")
|
||||
{
|
||||
return TokenType::LIST_START;
|
||||
}
|
||||
else if (text == "}")
|
||||
{
|
||||
return TokenType::LIST_END;
|
||||
}
|
||||
else if (text == ",")
|
||||
{
|
||||
return TokenType::COMMA;
|
||||
}
|
||||
else if (text == "+")
|
||||
{
|
||||
return TokenType::PLUS;
|
||||
}
|
||||
else if (text == "-")
|
||||
{
|
||||
return TokenType::MINUS;
|
||||
}
|
||||
else if (text == "*")
|
||||
{
|
||||
return TokenType::MULTIPLY;
|
||||
}
|
||||
else if (text == "/")
|
||||
{
|
||||
return TokenType::DIVIDE;
|
||||
}
|
||||
else if (text == "==")
|
||||
{
|
||||
return TokenType::EQ;
|
||||
}
|
||||
else if (text == "!=")
|
||||
{
|
||||
return TokenType::NEQ;
|
||||
}
|
||||
else if (text == "%")
|
||||
{
|
||||
return TokenType::MOD;
|
||||
}
|
||||
else if (text == "<")
|
||||
{
|
||||
return TokenType::LT;
|
||||
}
|
||||
else if (text == ">")
|
||||
{
|
||||
return TokenType::GT;
|
||||
}
|
||||
else if (text == "<=")
|
||||
{
|
||||
return TokenType::LTE;
|
||||
}
|
||||
else if (text == ">=")
|
||||
{
|
||||
return TokenType::GTE;
|
||||
}
|
||||
else if (text == "contains")
|
||||
{
|
||||
return TokenType::CONTAINS;
|
||||
}
|
||||
else if (text == "startswith")
|
||||
{
|
||||
return TokenType::STARTS_WITH;
|
||||
}
|
||||
else if (text == "endswith")
|
||||
{
|
||||
return TokenType::ENDS_WITH;
|
||||
}
|
||||
else if (text == "match")
|
||||
{
|
||||
return TokenType::MATCH;
|
||||
}
|
||||
else if (text == "!")
|
||||
{
|
||||
return TokenType::NOT;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((text.startsWith("r\"") || text.startsWith("ri\"")) &&
|
||||
|
@ -226,15 +274,21 @@ TokenType Tokenizer::tokenize(const QString &text)
|
|||
}
|
||||
|
||||
if (text.front() == '"' && text.back() == '"')
|
||||
{
|
||||
return TokenType::STRING;
|
||||
}
|
||||
|
||||
if (validIdentifiersMap.keys().contains(text))
|
||||
{
|
||||
return TokenType::IDENTIFIER;
|
||||
}
|
||||
|
||||
bool flag;
|
||||
if (text.toInt(&flag); flag)
|
||||
{
|
||||
return TokenType::INT;
|
||||
}
|
||||
}
|
||||
|
||||
return TokenType::NONE;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ static const QMap<QString, QString> validIdentifiersMap = {
|
|||
{"flags.whisper", "whisper message?"},
|
||||
{"flags.reply", "reply message?"},
|
||||
{"flags.automod", "automod message?"},
|
||||
{"flags.restricted", "restricted message?"},
|
||||
{"flags.monitored", "monitored message?"},
|
||||
{"message.content", "message text"},
|
||||
{"message.length", "message length"}};
|
||||
|
||||
|
|
|
@ -69,27 +69,39 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||
return 0;
|
||||
case MINUS:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() - right.toInt();
|
||||
}
|
||||
return 0;
|
||||
case MULTIPLY:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() * right.toInt();
|
||||
}
|
||||
return 0;
|
||||
case DIVIDE:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() / right.toInt();
|
||||
}
|
||||
return 0;
|
||||
case MOD:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() % right.toInt();
|
||||
}
|
||||
return 0;
|
||||
case OR:
|
||||
if (convertVariantTypes(left, right, QMetaType::Bool))
|
||||
{
|
||||
return left.toBool() || right.toBool();
|
||||
}
|
||||
return false;
|
||||
case AND:
|
||||
if (convertVariantTypes(left, right, QMetaType::Bool))
|
||||
{
|
||||
return left.toBool() && right.toBool();
|
||||
}
|
||||
return false;
|
||||
case EQ:
|
||||
if (variantTypesMatch(left, right, QMetaType::QString))
|
||||
|
@ -107,19 +119,27 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||
return !looselyCompareVariants(left, right);
|
||||
case LT:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() < right.toInt();
|
||||
}
|
||||
return false;
|
||||
case GT:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() > right.toInt();
|
||||
}
|
||||
return false;
|
||||
case LTE:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() <= right.toInt();
|
||||
}
|
||||
return false;
|
||||
case GTE:
|
||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||
{
|
||||
return left.toInt() >= right.toInt();
|
||||
}
|
||||
return false;
|
||||
case CONTAINS:
|
||||
if (variantIs(left, QMetaType::QStringList) &&
|
||||
|
@ -215,23 +235,31 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
|||
|
||||
// list must be two items
|
||||
if (list.size() != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// list must be a regular expression and an int
|
||||
if (variantIsNot(list.at(0),
|
||||
QMetaType::QRegularExpression) ||
|
||||
variantIsNot(list.at(1), QMetaType::Int))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto match =
|
||||
list.at(0).toRegularExpression().match(matching);
|
||||
|
||||
// if matched, return nth capture group. Otherwise, return ""
|
||||
if (match.hasMatch())
|
||||
{
|
||||
return match.captured(list.at(1).toInt());
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -263,9 +291,13 @@ PossibleType BinaryOperation::synthesizeType(const TypingContext &context) const
|
|||
{
|
||||
case PLUS:
|
||||
if (left == Type::String)
|
||||
{
|
||||
return TypeClass{Type::String}; // String concatenation
|
||||
}
|
||||
else if (left == Type::Int && right == Type::Int)
|
||||
{
|
||||
return TypeClass{Type::Int};
|
||||
}
|
||||
|
||||
return IllTyped{this, "Can only add Ints or concatenate a String"};
|
||||
case MINUS:
|
||||
|
@ -273,13 +305,17 @@ PossibleType BinaryOperation::synthesizeType(const TypingContext &context) const
|
|||
case DIVIDE:
|
||||
case MOD:
|
||||
if (left == Type::Int && right == Type::Int)
|
||||
{
|
||||
return TypeClass{Type::Int};
|
||||
}
|
||||
|
||||
return IllTyped{this, "Can only perform operation with Ints"};
|
||||
case OR:
|
||||
case AND:
|
||||
if (left == Type::Bool && right == Type::Bool)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
|
||||
return IllTyped{this,
|
||||
"Can only perform logical operations with Bools"};
|
||||
|
@ -292,37 +328,53 @@ PossibleType BinaryOperation::synthesizeType(const TypingContext &context) const
|
|||
case LTE:
|
||||
case GTE:
|
||||
if (left == Type::Int && right == Type::Int)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
|
||||
return IllTyped{this, "Can only perform comparisons with Ints"};
|
||||
case STARTS_WITH:
|
||||
case ENDS_WITH:
|
||||
if (isList(left))
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
if (left == Type::String && right == Type::String)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
|
||||
return IllTyped{
|
||||
this,
|
||||
"Can only perform starts/ends with a List or two Strings"};
|
||||
case CONTAINS:
|
||||
if (isList(left) || left == Type::Map)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
if (left == Type::String && right == Type::String)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
}
|
||||
|
||||
return IllTyped{
|
||||
this,
|
||||
"Can only perform contains with a List, a Map, or two Strings"};
|
||||
case MATCH: {
|
||||
if (left != Type::String)
|
||||
{
|
||||
return IllTyped{this,
|
||||
"Left argument of match must be a String"};
|
||||
}
|
||||
|
||||
if (right == Type::RegularExpression)
|
||||
{
|
||||
return TypeClass{Type::Bool};
|
||||
if (right == Type::MatchingSpecifier) // group capturing
|
||||
}
|
||||
if (right == Type::MatchingSpecifier)
|
||||
{ // group capturing
|
||||
return TypeClass{Type::String};
|
||||
}
|
||||
|
||||
return IllTyped{this, "Can only match on a RegularExpression or a "
|
||||
"MatchingSpecifier"};
|
||||
|
|
|
@ -53,7 +53,7 @@ void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item,
|
|||
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||
setColorItem(row[Column::Color], *item.getColor());
|
||||
|
||||
TwitchBadges::instance()->getBadgeIcon(
|
||||
getIApp()->getTwitchBadges()->getBadgeIcon(
|
||||
item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) {
|
||||
row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole);
|
||||
});
|
||||
|
|
|
@ -123,7 +123,9 @@ struct Deserialize<chatterino::HighlightBadge> {
|
|||
|
||||
auto _color = QColor(encodedColor);
|
||||
if (!_color.isValid())
|
||||
{
|
||||
_color = chatterino::HighlightBadge::FALLBACK_HIGHLIGHT_COLOR;
|
||||
}
|
||||
|
||||
return chatterino::HighlightBadge(_name, _displayName, _showInMentions,
|
||||
_hasAlert, _hasSound, _soundUrl,
|
||||
|
|
|
@ -204,6 +204,41 @@ void rebuildMessageHighlights(Settings &settings,
|
|||
{
|
||||
checks.emplace_back(highlightPhraseCheck(highlight));
|
||||
}
|
||||
|
||||
if (settings.enableAutomodHighlight)
|
||||
{
|
||||
const auto highlightSound =
|
||||
settings.enableAutomodHighlightSound.getValue();
|
||||
const auto highlightAlert =
|
||||
settings.enableAutomodHighlightTaskbar.getValue();
|
||||
const auto highlightSoundUrlValue =
|
||||
settings.automodHighlightSoundUrl.getValue();
|
||||
|
||||
checks.emplace_back(HighlightCheck{
|
||||
[=](const auto & /*args*/, const auto & /*badges*/,
|
||||
const auto & /*senderName*/, const auto & /*originalMessage*/,
|
||||
const auto &flags,
|
||||
const auto /*self*/) -> std::optional<HighlightResult> {
|
||||
if (!flags.has(MessageFlag::AutoModOffendingMessage))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<QUrl> highlightSoundUrl;
|
||||
if (!highlightSoundUrlValue.isEmpty())
|
||||
{
|
||||
highlightSoundUrl = highlightSoundUrlValue;
|
||||
}
|
||||
|
||||
return HighlightResult{
|
||||
highlightAlert, // alert
|
||||
highlightSound, // playSound
|
||||
highlightSoundUrl, // customSoundUrl
|
||||
nullptr, // color
|
||||
false, // showInMentions
|
||||
};
|
||||
}});
|
||||
}
|
||||
}
|
||||
|
||||
void rebuildUserHighlights(Settings &settings,
|
||||
|
@ -405,7 +440,8 @@ std::ostream &operator<<(std::ostream &os, const HighlightResult &result)
|
|||
return os;
|
||||
}
|
||||
|
||||
void HighlightController::initialize(Settings &settings, Paths & /*paths*/)
|
||||
void HighlightController::initialize(Settings &settings,
|
||||
const Paths & /*paths*/)
|
||||
{
|
||||
this->rebuildListener_.addSetting(settings.enableSelfHighlight);
|
||||
this->rebuildListener_.addSetting(settings.enableSelfHighlightSound);
|
||||
|
@ -434,6 +470,11 @@ void HighlightController::initialize(Settings &settings, Paths & /*paths*/)
|
|||
this->rebuildListener_.addSetting(settings.threadHighlightSoundUrl);
|
||||
this->rebuildListener_.addSetting(settings.showThreadHighlightInMentions);
|
||||
|
||||
this->rebuildListener_.addSetting(settings.enableAutomodHighlight);
|
||||
this->rebuildListener_.addSetting(settings.enableAutomodHighlightSound);
|
||||
this->rebuildListener_.addSetting(settings.enableAutomodHighlightTaskbar);
|
||||
this->rebuildListener_.addSetting(settings.automodHighlightSoundUrl);
|
||||
|
||||
this->rebuildListener_.setCB([this, &settings] {
|
||||
qCDebug(chatterinoHighlights)
|
||||
<< "Rebuild checks because a setting changed";
|
||||
|
|
|
@ -86,7 +86,7 @@ struct HighlightCheck {
|
|||
class HighlightController final : public Singleton
|
||||
{
|
||||
public:
|
||||
void initialize(Settings &settings, Paths &paths) override;
|
||||
void initialize(Settings &settings, const Paths &paths) override;
|
||||
|
||||
/**
|
||||
* @brief Checks the given message parameters if it matches our internal checks, and returns a result
|
||||
|
|
|
@ -98,9 +98,8 @@ void HighlightModel::afterInit()
|
|||
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||
setFilePathItem(whisperRow[Column::SoundPath], whisperSound, false);
|
||||
|
||||
// auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
||||
// setColorItem(whisperRow[Column::Color], *whisperColor, false);
|
||||
whisperRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags);
|
||||
auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
||||
setColorItem(whisperRow[Column::Color], *whisperColor, false);
|
||||
|
||||
this->insertCustomRow(whisperRow, HighlightRowIndexes::WhisperRow);
|
||||
|
||||
|
@ -234,6 +233,30 @@ void HighlightModel::afterInit()
|
|||
|
||||
this->insertCustomRow(threadMessageRow,
|
||||
HighlightRowIndexes::ThreadMessageRow);
|
||||
|
||||
// Highlight settings for automod caught messages
|
||||
const std::vector<QStandardItem *> automodRow = this->createRow();
|
||||
setBoolItem(automodRow[Column::Pattern],
|
||||
getSettings()->enableAutomodHighlight.getValue(), true, false);
|
||||
automodRow[Column::Pattern]->setData("AutoMod Caught Messages",
|
||||
Qt::DisplayRole);
|
||||
automodRow[Column::ShowInMentions]->setFlags({});
|
||||
setBoolItem(automodRow[Column::FlashTaskbar],
|
||||
getSettings()->enableAutomodHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(automodRow[Column::PlaySound],
|
||||
getSettings()->enableAutomodHighlightSound.getValue(), true,
|
||||
false);
|
||||
automodRow[Column::UseRegex]->setFlags({});
|
||||
automodRow[Column::CaseSensitive]->setFlags({});
|
||||
|
||||
const auto automodSound =
|
||||
QUrl(getSettings()->automodHighlightSoundUrl.getValue());
|
||||
setFilePathItem(automodRow[Column::SoundPath], automodSound, false);
|
||||
|
||||
automodRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags);
|
||||
|
||||
this->insertCustomRow(automodRow, HighlightRowIndexes::AutomodRow);
|
||||
}
|
||||
|
||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||
|
@ -278,6 +301,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableThreadHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::AutomodRow)
|
||||
{
|
||||
getSettings()->enableAutomodHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -336,6 +364,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableThreadHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::AutomodRow)
|
||||
{
|
||||
getSettings()->enableAutomodHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -377,6 +410,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableThreadHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::AutomodRow)
|
||||
{
|
||||
getSettings()->enableAutomodHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -412,6 +450,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->threadHighlightSoundUrl.setValue(
|
||||
value.toString());
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::AutomodRow)
|
||||
{
|
||||
getSettings()->automodHighlightSoundUrl.setValue(
|
||||
value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -419,48 +462,47 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
// Custom color
|
||||
if (role == Qt::DecorationRole)
|
||||
{
|
||||
auto colorName = value.value<QColor>().name(QColor::HexArgb);
|
||||
const auto setColor = [&](auto &setting, ColorType ty) {
|
||||
auto color = value.value<QColor>();
|
||||
setting.setValue(color.name(QColor::HexArgb));
|
||||
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||
.updateColor(ty, color);
|
||||
};
|
||||
|
||||
if (rowIndex == HighlightRowIndexes::SelfHighlightRow)
|
||||
{
|
||||
getSettings()->selfHighlightColor.setValue(colorName);
|
||||
setColor(getSettings()->selfHighlightColor,
|
||||
ColorType::SelfHighlight);
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::WhisperRow)
|
||||
{
|
||||
setColor(getSettings()->whisperHighlightColor,
|
||||
ColorType::Whisper);
|
||||
}
|
||||
// else if (rowIndex == HighlightRowIndexes::WhisperRow)
|
||||
// {
|
||||
// getSettings()->whisperHighlightColor.setValue(colorName);
|
||||
// }
|
||||
else if (rowIndex == HighlightRowIndexes::SubRow)
|
||||
{
|
||||
getSettings()->subHighlightColor.setValue(colorName);
|
||||
setColor(getSettings()->subHighlightColor,
|
||||
ColorType::Subscription);
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::RedeemedRow)
|
||||
{
|
||||
getSettings()->redeemedHighlightColor.setValue(colorName);
|
||||
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||
.updateColor(ColorType::RedeemedHighlight,
|
||||
QColor(colorName));
|
||||
setColor(getSettings()->redeemedHighlightColor,
|
||||
ColorType::RedeemedHighlight);
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::FirstMessageRow)
|
||||
{
|
||||
getSettings()->firstMessageHighlightColor.setValue(
|
||||
colorName);
|
||||
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||
.updateColor(ColorType::FirstMessageHighlight,
|
||||
QColor(colorName));
|
||||
setColor(getSettings()->firstMessageHighlightColor,
|
||||
ColorType::FirstMessageHighlight);
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::ElevatedMessageRow)
|
||||
{
|
||||
getSettings()->elevatedMessageHighlightColor.setValue(
|
||||
colorName);
|
||||
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||
.updateColor(ColorType::ElevatedMessageHighlight,
|
||||
QColor(colorName));
|
||||
setColor(getSettings()->elevatedMessageHighlightColor,
|
||||
ColorType::ElevatedMessageHighlight);
|
||||
}
|
||||
else if (rowIndex == HighlightRowIndexes::ThreadMessageRow)
|
||||
{
|
||||
getSettings()->threadHighlightColor.setValue(colorName);
|
||||
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||
.updateColor(ColorType::ThreadMessageHighlight,
|
||||
QColor(colorName));
|
||||
setColor(getSettings()->threadHighlightColor,
|
||||
ColorType::ThreadMessageHighlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ public:
|
|||
FirstMessageRow = 4,
|
||||
ElevatedMessageRow = 5,
|
||||
ThreadMessageRow = 6,
|
||||
AutomodRow = 7,
|
||||
};
|
||||
|
||||
enum UserHighlightRowIndexes {
|
||||
|
|
|
@ -164,7 +164,9 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||
|
||||
auto _color = QColor(encodedColor);
|
||||
if (!_color.isValid())
|
||||
{
|
||||
_color = chatterino::HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR;
|
||||
}
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _showInMentions, _hasAlert,
|
||||
_hasSound, _isRegex,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "UserHighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||
#include "providers/colors/ColorProvider.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
@ -10,8 +9,6 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
// commandmodel
|
||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(Column::COUNT, parent)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "common/SignalVectorModel.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
|
@ -12,6 +13,8 @@ class HighlightPhrase;
|
|||
class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||
{
|
||||
public:
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
explicit UserHighlightModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -66,7 +66,7 @@ std::vector<QShortcut *> HotkeyController::shortcutsForCategory(
|
|||
continue;
|
||||
}
|
||||
auto createShortcutFromKeySeq = [&](QKeySequence qs) {
|
||||
auto s = new QShortcut(qs, parent);
|
||||
auto *s = new QShortcut(qs, parent);
|
||||
s->setContext(hotkey->getContext());
|
||||
auto functionPointer = target->second;
|
||||
QObject::connect(s, &QShortcut::activated, parent,
|
||||
|
@ -101,7 +101,7 @@ void HotkeyController::save()
|
|||
|
||||
std::shared_ptr<Hotkey> HotkeyController::getHotkeyByName(QString name)
|
||||
{
|
||||
for (auto &hotkey : this->hotkeys_)
|
||||
for (const auto &hotkey : this->hotkeys_)
|
||||
{
|
||||
if (hotkey->name() == name)
|
||||
{
|
||||
|
@ -115,7 +115,7 @@ int HotkeyController::replaceHotkey(QString oldName,
|
|||
std::shared_ptr<Hotkey> newHotkey)
|
||||
{
|
||||
int i = 0;
|
||||
for (auto &hotkey : this->hotkeys_)
|
||||
for (const auto &hotkey : this->hotkeys_)
|
||||
{
|
||||
if (hotkey->name() == oldName)
|
||||
{
|
||||
|
@ -544,7 +544,7 @@ void HotkeyController::tryAddDefault(std::set<QString> &addedHotkeys,
|
|||
void HotkeyController::showHotkeyError(const std::shared_ptr<Hotkey> &hotkey,
|
||||
QString warning)
|
||||
{
|
||||
auto msgBox = new QMessageBox(
|
||||
auto *msgBox = new QMessageBox(
|
||||
QMessageBox::Icon::Warning, "Hotkey error",
|
||||
QString(
|
||||
"There was an error while executing your hotkey named \"%1\": \n%2")
|
||||
|
|
|
@ -32,9 +32,11 @@ bool isIgnoredMessage(IgnoredMessageParameters &¶ms)
|
|||
{
|
||||
auto sourceUserID = params.twitchUserID;
|
||||
|
||||
bool isBlocked =
|
||||
getApp()->accounts->twitch.getCurrent()->blockedUserIds().contains(
|
||||
sourceUserID);
|
||||
bool isBlocked = getIApp()
|
||||
->getAccounts()
|
||||
->twitch.getCurrent()
|
||||
->blockedUserIds()
|
||||
.contains(sourceUserID);
|
||||
if (isBlocked)
|
||||
{
|
||||
switch (static_cast<ShowIgnoredUsersMessages>(
|
||||
|
|
|
@ -143,12 +143,16 @@ const std::optional<ImagePtr> &ModerationAction::getImage() const
|
|||
if (this->imageToLoad_ != 0)
|
||||
{
|
||||
if (this->imageToLoad_ == 1)
|
||||
{
|
||||
this->image_ =
|
||||
Image::fromResourcePixmap(getResources().buttons.ban);
|
||||
}
|
||||
else if (this->imageToLoad_ == 2)
|
||||
{
|
||||
this->image_ =
|
||||
Image::fromResourcePixmap(getResources().buttons.trashCan);
|
||||
}
|
||||
}
|
||||
|
||||
return this->image_;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
namespace chatterino {
|
||||
|
||||
void NotificationController::initialize(Settings &settings, Paths &paths)
|
||||
void NotificationController::initialize(Settings &settings, const Paths &paths)
|
||||
{
|
||||
this->initialized_ = true;
|
||||
for (const QString &channelName : this->twitchSetting_.getValue())
|
||||
|
@ -225,7 +225,7 @@ void NotificationController::removeFakeChannel(const QString channelName)
|
|||
|
||||
for (int i = snapshotLength - 1; i >= end; --i)
|
||||
{
|
||||
auto &s = snapshot[i];
|
||||
const auto &s = snapshot[i];
|
||||
|
||||
if (s->messageText == liveMessageSearchText)
|
||||
{
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue