Merge branch 'master' of github.com:Chatterino/chatterino2 into chore/leak-less-memory-lua

This commit is contained in:
Mm2PL 2024-09-02 20:18:25 +02:00
commit 9f5a287861
No known key found for this signature in database
GPG key ID: 94AC9B80EFA15ED9
363 changed files with 10699 additions and 10721 deletions

View file

@ -12,7 +12,7 @@ install_prefix="appdir/usr"
# The directory we finally pack into our .deb package
packaging_dir="package"
# Get the Ubuntu Release (e.g. 20.04 or 22.04)
# Get the Ubuntu Release (e.g. 20.04, 22.04 or 24.04)
ubuntu_release="$(lsb_release -rs)"
# The final path where we'll save the .deb package
@ -22,11 +22,15 @@ deb_path="Chatterino-ubuntu-${ubuntu_release}-x86_64.deb"
case "$ubuntu_release" in
20.04)
# Qt6 static-linked deb, see https://github.com/Chatterino/docker
dependencies="libc6, libstdc++6, libblkid1, libbsd0, libc6, libexpat1, libffi7, libfontconfig1, libfreetype6, libglib2.0-0, libglvnd0, libglx0, libgraphite2-3, libharfbuzz0b, libicu66, libjpeg-turbo8, libmount1, libopengl0, libpcre2-16-0, libpcre3, libpng16-16, libselinux1, libssl1.1, libstdc++6, libuuid1, libx11-xcb1, libxau6, libxcb1, libxcb-cursor0, libxcb-glx0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render0, libxcb-render-util0, libxcb-shape0, libxcb-shm0, libxcb-sync1, libxcb-util1, libxcb-xfixes0, libxcb-xkb1, libxdmcp6, libxkbcommon0, libxkbcommon-x11-0, zlib1g"
dependencies="libc6, libstdc++6, libblkid1, libbsd0, libexpat1, libffi7, libfontconfig1, libfreetype6, libglib2.0-0, libglvnd0, libglx0, libgraphite2-3, libharfbuzz0b, libicu66, libjpeg-turbo8, libmount1, libopengl0, libpcre2-16-0, libpcre3, libpng16-16, libselinux1, libssl1.1, libuuid1, libx11-xcb1, libxau6, libxcb1, libxcb-cursor0, libxcb-glx0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render0, libxcb-render-util0, libxcb-shape0, libxcb-shm0, libxcb-sync1, libxcb-util1, libxcb-xfixes0, libxcb-xkb1, libxdmcp6, libxkbcommon0, libxkbcommon-x11-0, zlib1g"
;;
22.04)
# Qt6 static-linked deb, see https://github.com/Chatterino/docker
dependencies="libc6, libstdc++6, libglx0, libopengl0, libpng16-16, libharfbuzz0b, libfreetype6, libfontconfig1, libjpeg-turbo8, libxcb-glx0, libegl1, libx11-6, libxkbcommon0, libx11-xcb1, libxkbcommon-x11-0, libxcb-cursor0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render-util0, libxcb-shm0, libxcb-sync1, libxcb-xfixes0, libxcb-render0, libxcb-shape0, libxcb-xkb1, libxcb1, libbrotli1, libglib2.0-0, zlib1g, libicu70, libpcre2-16-0, libssl3, libgraphite2-3, libexpat1, libuuid1, libxcb-util1, libxau6, libxdmcp6, libbrotli1, libffi8, libmount1, libselinux1, libpcre3, libicu70, libbsd0, libblkid1, libpcre2-8-0, libmd0"
dependencies="libc6, libstdc++6, libglx0, libopengl0, libpng16-16, libharfbuzz0b, libfreetype6, libfontconfig1, libjpeg-turbo8, libxcb-glx0, libegl1, libx11-6, libxkbcommon0, libx11-xcb1, libxkbcommon-x11-0, libxcb-cursor0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render-util0, libxcb-shm0, libxcb-sync1, libxcb-xfixes0, libxcb-render0, libxcb-shape0, libxcb-xkb1, libxcb1, libbrotli1, libglib2.0-0, zlib1g, libicu70, libpcre2-16-0, libssl3, libgraphite2-3, libexpat1, libuuid1, libxcb-util1, libxau6, libxdmcp6, libffi8, libmount1, libselinux1, libpcre3, libbsd0, libblkid1, libpcre2-8-0, libmd0"
;;
24.04)
# Qt6 static-linked deb, see https://github.com/Chatterino/docker
dependencies="libc6, libstdc++6, libglx0, libopengl0, libpng16-16, libharfbuzz0b, libfreetype6, libfontconfig1, libjpeg-turbo8, libxcb-glx0, libegl1, libx11-6, libxkbcommon0, libx11-xcb1, libxkbcommon-x11-0, libxcb-cursor0, libxcb-icccm4, libxcb-image0, libxcb-keysyms1, libxcb-randr0, libxcb-render-util0, libxcb-shm0, libxcb-sync1, libxcb-xfixes0, libxcb-render0, libxcb-shape0, libxcb-xkb1, libxcb1, libbrotli1, libglib2.0-0, zlib1g, libicu74, libpcre2-16-0, libssl3, libgraphite2-3, libexpat1, libuuid1, libxcb-util1, libxau6, libxdmcp6, libffi8, libmount1, libselinux1, libpcre3, libbsd0, libblkid1, libpcre2-8-0, libmd0"
;;
*)
echo "Unsupported Ubuntu release $ubuntu_release"

View file

@ -0,0 +1,61 @@
from datetime import datetime, timezone
import os
import subprocess
import re
LINE_REGEX = re.compile(
r"""(?x)
^(?P<commit>[A-Fa-f0-9]+)\s+
\(
<(?P<email>[^>]+)>\s+
(?P<date>[^\s]+\s[^\s]+\s[^\s]+)\s+
(?P<line>\d+)
\)\s
(?P<content>.*)$
"""
)
VERSION_REGEX = re.compile(r"^#+\s*v?\d")
# contains lines in the form of
# {commit-sha} (<{email}>\s+{date}\s+{line-no}) {line}
p = subprocess.run(
["git", "blame", "-e", "--date=iso", "../CHANGELOG.md"],
cwd=os.path.dirname(os.path.realpath(__file__)),
text=True,
check=True,
capture_output=True,
)
unreleased_lines: list[tuple[datetime, str]] = []
for line in p.stdout.splitlines():
if not line:
continue
m = LINE_REGEX.match(line)
assert m, f"Failed to match '{line}'"
content = m.group("content")
if not content:
continue
if content.startswith("#"):
if VERSION_REGEX.match(content):
break
continue # ignore lines with '#'
d = datetime.fromisoformat(m.group("date"))
d = d.astimezone(tz=timezone.utc)
content = content.replace("- ", f"- [{d.strftime('%Y-%m-%d')}] ", 1)
unreleased_lines.append((d, content))
unreleased_lines.sort(key=lambda it: it[0], reverse=True)
if len(unreleased_lines) == 0:
print("No changes since last release.")
for _, line in unreleased_lines[:5]:
print(line)
if len(unreleased_lines) > 5:
print("<details><summary>More Changes</summary>\n")
for _, line in unreleased_lines[5:]:
print(line)
print("</details>")

View file

@ -42,7 +42,7 @@ CheckOptions:
- key: readability-identifier-naming.FunctionCase
value: camelBack
- key: readability-identifier-naming.FunctionIgnoredRegexp
value: ^TEST$
value: ^(TEST|MOCK_METHOD)$
- key: readability-identifier-naming.MemberCase
value: camelBack
@ -73,3 +73,6 @@ CheckOptions:
- key: misc-const-correctness.AnalyzeValues
value: false
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
value: true

7
.codecov.yml Normal file
View file

@ -0,0 +1,7 @@
comment: false
ignore:
- "/usr/local*/**/*"
- "/usr/include/**/*"
- "lib/"
- "**/ui_*.h"
- "**/moc_*.cpp"

View file

@ -33,7 +33,7 @@ jobs:
include:
- os: ubuntu-20.04
container: ghcr.io/chatterino/chatterino2-build-ubuntu-20.04:latest
qt-version: 6.6.1
qt-version: 6.7.2
force-lto: false
plugins: true
skip-artifact: false
@ -42,13 +42,22 @@ jobs:
build-deb: true
- os: ubuntu-22.04
container: ghcr.io/chatterino/chatterino2-build-ubuntu-22.04:latest
qt-version: 6.6.1
qt-version: 6.7.2
force-lto: false
plugins: true
skip-artifact: false
skip-crashpad: false
build-appimage: true
build-deb: true
- os: ubuntu-24.04
container: ghcr.io/chatterino/chatterino2-build-ubuntu-24.04:latest
qt-version: 6.7.2
force-lto: false
plugins: true
skip-artifact: false
skip-crashpad: false
build-appimage: false
build-deb: true
env:
C2_ENABLE_LTO: ${{ matrix.force-lto }}
C2_PLUGINS: ${{ matrix.plugins }}
@ -176,7 +185,7 @@ jobs:
- name: Setup sccache (Windows)
# sccache v0.7.4
uses: hendrikmuhs/ccache-action@v1.2.13
uses: hendrikmuhs/ccache-action@v1.2.14
if: startsWith(matrix.os, 'windows')
with:
variant: sccache
@ -360,19 +369,25 @@ jobs:
- uses: actions/download-artifact@v4
name: Linux AppImage
with:
name: Chatterino-x86_64-Qt-6.6.1.AppImage
name: Chatterino-x86_64-Qt-6.7.2.AppImage
path: release-artifacts/
- uses: actions/download-artifact@v4
name: Ubuntu 20.04 deb
with:
name: Chatterino-ubuntu-20.04-Qt-6.6.1.deb
name: Chatterino-ubuntu-20.04-Qt-6.7.2.deb
path: release-artifacts/
- uses: actions/download-artifact@v4
name: Ubuntu 22.04 deb
with:
name: Chatterino-ubuntu-22.04-Qt-6.6.1.deb
name: Chatterino-ubuntu-22.04-Qt-6.7.2.deb
path: release-artifacts/
- uses: actions/download-artifact@v4
name: Ubuntu 24.04 deb
with:
name: Chatterino-ubuntu-24.04-Qt-6.7.2.deb
path: release-artifacts/
- name: Copy flatpakref
@ -389,6 +404,22 @@ jobs:
working-directory: release-artifacts
shell: bash
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Format changes
id: format-changes
run: |
delimiter=$(openssl rand -hex 32)
{
echo "changelog<<$delimiter"
python3 ./.CI/format-recent-changes.py
echo $delimiter
} >> "$GITHUB_OUTPUT"
shell: bash
- name: Create release
uses: ncipollo/release-action@v1.14.0
with:
@ -396,7 +427,7 @@ jobs:
allowUpdates: true
artifactErrorsFailBuild: true
artifacts: "release-artifacts/*"
body: ${{ github.event.head_commit.message }}
body: ${{ steps.format-changes.outputs.changelog }}
prerelease: true
name: Nightly Release
tag: nightly-build

View file

@ -26,7 +26,7 @@ jobs:
run: sudo apt-get -y install dos2unix
- name: Check formatting
uses: DoozyX/clang-format-lint-action@v0.17
uses: DoozyX/clang-format-lint-action@v0.18
with:
source: "./src ./tests/src ./benchmarks/src ./mocks/include"
extensions: "hpp,cpp"

View file

@ -45,7 +45,7 @@ jobs:
build_dir: build-clang-tidy
config_file: ".clang-tidy"
split_workflow: true
exclude: "lib/*,tools/crash-handler/*"
exclude: "lib/*,tools/crash-handler/*,mocks/*"
cmake_command: >-
./.CI/setup-clang-tidy.sh
apt_packages: >-

View file

@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
os: [macos-13]
qt-version: [5.15.2, 6.5.0]
qt-version: [5.15.2, 6.7.1]
plugins: [false]
fail-fast: false
env:

View file

@ -69,7 +69,7 @@ jobs:
- name: Setup sccache
# sccache v0.7.4
uses: hendrikmuhs/ccache-action@v1.2.13
uses: hendrikmuhs/ccache-action@v1.2.14
with:
variant: sccache
# only save on the default (master) branch

View file

@ -5,6 +5,10 @@ on:
pull_request:
workflow_dispatch:
merge_group:
push:
branches:
- master
- main
env:
TWITCH_PUBSUB_SERVER_TAG: v1.0.7
@ -24,7 +28,7 @@ jobs:
include:
- os: "ubuntu-22.04"
container: ghcr.io/chatterino/chatterino2-build-ubuntu-22.04:latest
qt-version: 6.6.1
qt-version: 6.7.1
plugins: true
fail-fast: false
env:
@ -39,11 +43,11 @@ jobs:
- name: Create build directory (Ubuntu)
run: mkdir build-test
- name: Install googlebench
- name: Install dependencies
run: |
sudo apt update
sudo apt -y install \
libbenchmark-dev
sudo DEBIAN_FRONTEND=noninteractive apt -y --no-install-recommends install \
libbenchmark-dev gcovr gnupg
- name: Build (Ubuntu)
run: |
@ -55,6 +59,8 @@ jobs:
-DCMAKE_PREFIX_PATH="$Qt6_DIR/lib/cmake" \
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
-DCHATTERINO_STATIC_QT_BUILD=On \
-DCHATTERINO_GENERATE_COVERAGE=On \
-DCMAKE_BUILD_TYPE=Debug \
..
cmake --build .
working-directory: build-test
@ -84,3 +90,11 @@ jobs:
cd ../build-test
ctest --repeat until-pass:4 --output-on-failure --exclude-regex ClassicEmoteNameFiltering
working-directory: build-test
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4.5.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
plugin: gcov
fail_ci_if_error: true
verbose: true

View file

@ -4,6 +4,8 @@
- Major: Release plugins alpha. (#5288)
- Major: Improve high-DPI support on Windows. (#4868, #5391)
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
- Minor: Add option to customise Moderation buttons with images. (#5369)
- Minor: Colored usernames now update on the fly when changing the "Color @usernames" setting. (#5300)
- Minor: Added `flags.action` filter variable, allowing you to filter on `/me` messages. (#5397)
@ -15,8 +17,18 @@
- Minor: Added support for Brave & google-chrome-stable browsers. (#5452)
- Minor: Added drop indicator line while dragging in tables. (#5256)
- Minor: Add channel points indication for new bits power-up redemptions. (#5471)
- Minor: Added option to log streams by their ID, allowing for easier "per-stream" log analyzing. (#5507)
- Minor: Added `/warn <username> <reason>` command for mods. This prevents the user from chatting until they acknowledge the warning. (#5474)
- Minor: Introduce HTTP API for plugins. (#5383)
- Minor: Added option to suppress live notifictions on startup. (#5388)
- Minor: Improve appearance of reply button. (#5491)
- Minor: Introduce HTTP API for plugins. (#5383, #5492, #5494)
- Minor: Support more Firefox variants for incognito link opening. (#5503)
- Minor: Replying to a message will now display the message being replied to. (#4350, #5519)
- Minor: Links can now have prefixes and suffixes such as parentheses. (#5486, #5515)
- Minor: Added support for scrolling in splits with touchscreen panning gestures. (#5524)
- Minor: Removed experimental IRC support. (#5547)
- Minor: Moderators can now see which mods start and cancel raids. (#5563)
- Minor: The emote popup now reloads when Twitch emotes are reloaded. (#5580)
- Bugfix: Fixed tab move animation occasionally failing to start after closing a tab. (#5426)
- Bugfix: If a network request errors with 200 OK, Qt's error code is now reported instead of the HTTP status. (#5378)
- Bugfix: Fixed restricted users usernames not being clickable. (#5405)
@ -24,6 +36,17 @@
- Bugfix: Fixed message history occasionally not loading after a sleep. (#5457)
- Bugfix: Fixed a crash when tab completing while having an invalid plugin loaded. (#5401)
- Bugfix: Fixed windows on Windows not saving correctly when snapping them to the edges. (#5478)
- Bugfix: Fixed user info card popups adding duplicate line to log files. (#5499)
- Bugfix: Fixed tooltips and input completion popups not working after moving a split. (#5541, #5576)
- Bugfix: Fixed rare issue on shutdown where the client would hang. (#5557)
- Bugfix: Fixed `/clearmessages` not working with more than one window. (#5489)
- Bugfix: Fixed splits staying paused after unfocusing Chatterino in certain configurations. (#5504)
- Bugfix: Links with invalid characters in the domain are no longer detected. (#5509)
- Bugfix: Fixed janky selection for messages with RTL segments (selection is still wrong, but consistently wrong). (#5525)
- Bugfix: Fixed event emotes not showing up in autocomplete and popups. (#5239, #5580)
- Bugfix: Fixed tab visibility being controllable in the emote popup. (#5530)
- Bugfix: Fixed account switch not being saved if no other settings were changed. (#5558)
- Bugfix: Fixed some tooltips not being readable. (#5578)
- Dev: Update Windows build from Qt 6.5.0 to Qt 6.7.1. (#5420)
- Dev: Update vcpkg build Qt from 6.5.0 to 6.7.0, boost from 1.83.0 to 1.85.0, openssl from 3.1.3 to 3.3.0. (#5422)
- Dev: Unsingletonize `ISoundController`. (#5462)
@ -34,14 +57,33 @@
- Dev: Refactor `TwitchIrcServer`, making it abstracted. (#5421, #5435)
- Dev: Reduced the amount of scale events. (#5404, #5406)
- Dev: Removed unused timegate settings. (#5361)
- Dev: Add `Channel::addSystemMessage` helper function, allowing us to avoid the common `channel->addMessage(makeSystemMessage(...));` pattern. (#5500)
- Dev: Unsingletonize `Resources2`. (#5460)
- Dev: All Lua globals now show in the `c2` global in the LuaLS metadata. (#5385)
- Dev: Images are now loaded in worker threads. (#5431)
- Dev: Fixed broken `SignalVector::operator[]` implementation. (#5556)
- Dev: Qt Creator now auto-configures Conan when loading the project and skips vcpkg. (#5305)
- Dev: The MSVC CRT is now bundled with Chatterino as it depends on having a recent version installed. (#5447)
- Dev: Refactor/unsingletonize `UserDataController`. (#5459)
- Dev: Cleanup `BrowserExtension`. (#5465)
- Dev: Deprecate Qt 5.12. (#5396)
- Dev: Refactored `MessageFlag` into its own file. (#5549)
- Dev: The running Qt version is now shown in the about page if it differs from the compiled version. (#5501)
- Dev: `FlagsEnum` is now `constexpr`. (#5510)
- Dev: Documented and added tests to RTL handling. (#5473)
- Dev: Refactored 7TV/BTTV definitions out of `TwitchIrcServer` into `Application`. (#5532)
- Dev: Refactored code that's responsible for deleting old update files. (#5535)
- Dev: Cleanly exit on shutdown. (#5537)
- Dev: Removed the `getTwitchAbstract` method in `Application`. (#5560)
- Dev: Renamed threads created by Chatterino on Linux and Windows. (#5538, #5539, #5544)
- Dev: Refactored a few `#define`s into `const(expr)` and cleaned includes. (#5527)
- Dev: Added `FlagsEnum::isEmpty`. (#5550)
- Dev: Prepared for Qt 6.8 by addressing some deprecations. (#5529)
- Dev: Moved some responsibility away from Application into WindowManager. (#5551)
- Dev: Fixed benchmarks segfaulting on run. (#5559)
- Dev: Refactored `MessageBuilder` to be a single class. (#5548)
- Dev: Recent changes are now shown in the nightly release description. (#5553, #5554)
- Dev: The timer for `StreamerMode` is now destroyed on the correct thread. (#5571)
## 2.5.1

View file

@ -4,9 +4,9 @@
#include "controllers/highlights/HighlightController.hpp"
#include "controllers/highlights/HighlightPhrase.hpp"
#include "messages/Message.hpp"
#include "messages/SharedMessageBuilder.hpp"
#include "mocks/EmptyApplication.hpp"
#include "singletons/Settings.hpp"
#include "messages/MessageBuilder.hpp"
#include "mocks/BaseApplication.hpp"
#include "mocks/UserData.hpp"
#include "util/Helpers.hpp"
#include <benchmark/benchmark.h>
@ -16,15 +16,16 @@
using namespace chatterino;
class BenchmarkMessageBuilder : public SharedMessageBuilder
class BenchmarkMessageBuilder : public MessageBuilder
{
public:
explicit BenchmarkMessageBuilder(
Channel *_channel, const Communi::IrcPrivateMessage *_ircMessage,
const MessageParseArgs &_args)
: SharedMessageBuilder(_channel, _ircMessage, _args)
: MessageBuilder(_channel, _ircMessage, _args)
{
}
virtual MessagePtr build()
{
// PARSE
@ -47,9 +48,14 @@ public:
}
};
class MockApplication : mock::EmptyApplication
class MockApplication : public mock::BaseApplication
{
public:
MockApplication()
: highlights(this->settings, &this->accounts)
{
}
AccountController *getAccounts() override
{
return &this->accounts;
@ -59,16 +65,19 @@ public:
return &this->highlights;
}
IUserDataController *getUserData() override
{
return &this->userData;
}
AccountController accounts;
HighlightController highlights;
// TODO: Figure this out
mock::UserDataController userData;
};
static void BM_HighlightTest(benchmark::State &state)
{
MockApplication mockApplication;
QTemporaryDir settingsDir;
Settings settings(settingsDir.path());
std::string message =
R"(@badge-info=subscriber/34;badges=moderator/1,subscriber/24;color=#FF0000;display-name=테스트계정420;emotes=41:6-13,15-22;flags=;id=a3196c7e-be4c-4b49-9c5a-8b8302b50c2a;mod=1;room-id=11148817;subscriber=1;tmi-sent-ts=1590922213730;turbo=0;user-id=117166826;user-type=mod :testaccount_420!testaccount_420@testaccount_420.tmi.twitch.tv PRIVMSG #pajlada :-tags Kreygasm,Kreygasm (no space))";

View file

@ -5,8 +5,6 @@
#include <QString>
#include <QStringList>
#include <optional>
using namespace chatterino;
const QString INPUT = QStringLiteral(
@ -14,8 +12,9 @@ const QString INPUT = QStringLiteral(
"(or 2.4.2 if its out) "
"https://github.com/Chatterino/chatterino2/releases/tag/nightly-build "
"AlienPls https://www.youtube.com/watch?v=ELBBiBDcWc0 "
"127.0.3 aaaa xd 256.256.256.256 AsdQwe xd 127.0.0.1 https://. https://.be "
"https://a http://a.b https://a.be ftp://xdd.com "
"127.0.3 aaaa xd 256.256.256.256 AsdQwe xd 127.0.0.1 https://. "
"*https://.be "
"https://a: http://a.b (https://a.be) ftp://xdd.com "
"this is a text lol . ://foo.com //aa.de :/foo.de xd.XDDDDDD ");
static void BM_LinkParsing(benchmark::State &state)
@ -24,15 +23,15 @@ static void BM_LinkParsing(benchmark::State &state)
// Make sure the TLDs are loaded
{
benchmark::DoNotOptimize(LinkParser("xd.com").result());
benchmark::DoNotOptimize(linkparser::parse("xd.com"));
}
for (auto _ : state)
{
for (auto word : words)
for (const auto &word : words)
{
LinkParser parser(word);
benchmark::DoNotOptimize(parser.result());
auto parsed = linkparser::parse(word);
benchmark::DoNotOptimize(parsed);
}
}
}

View file

@ -2,8 +2,8 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/highlights/HighlightController.hpp"
#include "messages/Emote.hpp"
#include "mocks/BaseApplication.hpp"
#include "mocks/DisabledStreamerMode.hpp"
#include "mocks/EmptyApplication.hpp"
#include "mocks/LinkResolver.hpp"
#include "mocks/TwitchIrcServer.hpp"
#include "mocks/UserData.hpp"
@ -32,9 +32,14 @@ using namespace literals;
namespace {
class MockApplication : mock::EmptyApplication
class MockApplication : public mock::BaseApplication
{
public:
MockApplication()
: highlights(this->settings, &this->accounts)
{
}
IEmotes *getEmotes() override
{
return &this->emotes;

View file

@ -1,3 +1,4 @@
#include "common/Args.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
@ -16,10 +17,12 @@ int main(int argc, char **argv)
::benchmark::Initialize(&argc, argv);
Args args;
// Ensure settings are initialized before any benchmarks are run
QTemporaryDir settingsDir;
settingsDir.setAutoRemove(false); // we'll remove it manually
chatterino::Settings settings(settingsDir.path());
chatterino::Settings settings(args, settingsDir.path());
QTimer::singleShot(0, [&]() {
::benchmark::RunSpecifiedBenchmarks();

View file

@ -51,7 +51,7 @@
"type": "object",
"properties": {
"type": {
"enum": ["FilesystemRead", "FilesystemWrite"]
"enum": ["FilesystemRead", "FilesystemWrite", "Network"]
}
}
}

View file

@ -158,12 +158,27 @@ function c2.Channel.by_twitch_id(id) end
-- End src/controllers/plugins/api/ChannelRef.hpp
-- Begin src/controllers/plugins/api/HTTPRequest.hpp
-- Begin src/controllers/plugins/api/HTTPResponse.hpp
---@class HTTPResponse
---@field data string Data received from the server
---@field status integer? HTTP Status code returned by the server
---@field error string A somewhat human readable description of an error if such happened
HTTPResponse = {}
--- Returns the data. This is not guaranteed to be encoded using any
--- particular encoding scheme. It's just the bytes the server returned.
---
function HTTPResponse:data() end
--- Returns the status code.
---
function HTTPResponse:status() end
--- A somewhat human readable description of an error if such happened
---
function HTTPResponse:error() end
-- End src/controllers/plugins/api/HTTPResponse.hpp
-- Begin src/controllers/plugins/api/HTTPRequest.hpp
---@alias HTTPCallback fun(result: HTTPResponse): nil
---@class HTTPRequest
@ -213,26 +228,6 @@ function HTTPRequest.create(method, url) end
-- End src/controllers/plugins/api/HTTPRequest.hpp
-- Begin src/controllers/plugins/api/HTTPResponse.hpp
---@class HTTPResponse
HTTPResponse = {}
--- Returns the data. This is not guaranteed to be encoded using any
--- particular encoding scheme. It's just the bytes the server returned.
---
function HTTPResponse:data() end
--- Returns the status code.
---
function HTTPResponse:status() end
--- A somewhat human readable description of an error if such happened
---
function HTTPResponse:error() end
-- End src/controllers/plugins/api/HTTPResponse.hpp
-- Begin src/common/network/NetworkCommon.hpp
---@alias HTTPMethod integer

View file

@ -420,14 +420,6 @@ It returns something like: `"ConnectionRefusedError"`, `"401"`.
This function returns the HTTP status code of the request or `nil` if there was
an error before a status code could be received.
```lua
{
data = "This is the data received from the server as a string",
status = 200, -- HTTP status code returned by the server or nil if no response was received because of an error
error = "A somewhat human readable description of an error if such happened"
}
```
#### `HTTPRequest`
Allows you to send an HTTP request to a URL. Do not create requests that you
@ -443,7 +435,7 @@ containing a valid URL (ex. `https://example.com/path/to/api`).
```lua
local req = c2.HTTPRequest.create(c2.HTTPMethod.Get, "https://example.com")
req:on_success(function (res)
print(res.data)
print(res:data())
end)
req:execute()
```
@ -496,12 +488,12 @@ request:set_header("Content-Type", "text/plain")
request:on_success(function (res)
print('Success!')
-- Data is in res.data
print(res.status)
print(res:status())
end)
request:on_error(function (res)
print('Error!')
print(res.status)
print(res.error)
print(res:status())
print(res:error())
end)
request:finally(function ()
print('Finally')

@ -1 +1 @@
Subproject commit 821c4818ade1aa4da56ac753285c159ce26fd597
Subproject commit a78ce469b456c06103b3b30d4bd37e7bb80da30c

@ -1 +1 @@
Subproject commit 182165b584dad130afaf4bcd25b8629799baea38
Subproject commit f339d2f73730f8fee4412f5e4938717866ecef48

@ -1 +1 @@
Subproject commit f3e9161ee61b47da71c7858e24efee9b0053357f
Subproject commit 7011003eabf6ac95c19f523968a72771fc177fcd

View file

@ -0,0 +1,76 @@
#pragma once
#include "common/Args.hpp"
#include "mocks/DisabledStreamerMode.hpp"
#include "mocks/EmptyApplication.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include <QString>
namespace chatterino::mock {
/**
* BaseApplication intends to be a mock application with a few more sane defaults, but with less configurability
*/
class BaseApplication : public EmptyApplication
{
public:
BaseApplication()
: settings(this->args, this->settingsDir.path())
, updates(this->paths_, this->settings)
, theme(this->paths_)
, fonts(this->settings)
{
}
explicit BaseApplication(const QString &settingsData)
: EmptyApplication(settingsData)
, settings(this->args, this->settingsDir.path())
, updates(this->paths_, this->settings)
, theme(this->paths_)
, fonts(this->settings)
{
}
Updates &getUpdates() override
{
return this->updates;
}
IStreamerMode *getStreamerMode() override
{
return &this->streamerMode;
}
Theme *getThemes() override
{
return &this->theme;
}
Fonts *getFonts() override
{
return &this->fonts;
}
BttvLiveUpdates *getBttvLiveUpdates() override
{
return nullptr;
}
SeventvEventAPI *getSeventvEventAPI() override
{
return nullptr;
}
Args args;
Settings settings;
Updates updates;
DisabledStreamerMode streamerMode;
Theme theme;
Fonts fonts;
};
} // namespace chatterino::mock

View file

@ -9,4 +9,8 @@ public:
{
return false;
}
void start() override
{
}
};

View file

@ -12,12 +12,18 @@ namespace chatterino::mock {
class EmptyApplication : public IApplication
{
public:
EmptyApplication()
: updates_(this->paths_)
EmptyApplication() = default;
explicit EmptyApplication(const QString &settingsData)
{
QFile settingsFile(this->settingsDir.filePath("settings.json"));
settingsFile.open(QIODevice::WriteOnly | QIODevice::Text);
settingsFile.write(settingsData.toUtf8());
settingsFile.flush();
settingsFile.close();
}
virtual ~EmptyApplication() = default;
~EmptyApplication() override = default;
bool isTest() const override
{
@ -123,13 +129,6 @@ public:
return nullptr;
}
IAbstractIrcServer *getTwitchAbstract() override
{
assert(false && "EmptyApplication::getTwitchAbstract was called "
"without being initialized");
return nullptr;
}
PubSub *getTwitchPubSub() override
{
assert(false && "getTwitchPubSub was called without being initialized");
@ -209,11 +208,6 @@ public:
}
#endif
Updates &getUpdates() override
{
return this->updates_;
}
BttvEmotes *getBttvEmotes() override
{
assert(false && "EmptyApplication::getBttvEmotes was called without "
@ -221,6 +215,13 @@ public:
return nullptr;
}
BttvLiveUpdates *getBttvLiveUpdates() override
{
assert(false && "EmptyApplication::getBttvLiveUpdates was called "
"without being initialized");
return nullptr;
}
FfzEmotes *getFfzEmotes() override
{
assert(false && "EmptyApplication::getFfzEmotes was called without "
@ -235,6 +236,13 @@ public:
return nullptr;
}
SeventvEventAPI *getSeventvEventAPI() override
{
assert(false && "EmptyApplication::getSeventvEventAPI was called "
"without being initialized");
return nullptr;
}
ILinkResolver *getLinkResolver() override
{
assert(false && "EmptyApplication::getLinkResolver was called without "
@ -249,11 +257,16 @@ public:
return nullptr;
}
protected:
ITwitchUsers *getTwitchUsers() override
{
assert(false && "EmptyApplication::getTwitchUsers was called without "
"being initialized");
return nullptr;
}
QTemporaryDir settingsDir;
Paths paths_;
Args args_;
Updates updates_;
};
} // namespace chatterino::mock

View file

@ -410,6 +410,23 @@ public:
(FailureCallback<HelixSendMessageError, QString> failureCallback)),
(override));
// get user emotes
MOCK_METHOD(
void, getUserEmotes,
(QString userID, QString broadcasterID,
(ResultCallback<std::vector<HelixChannelEmote>, HelixPaginationState>
successCallback),
FailureCallback<QString> failureCallback, CancellationToken &&token),
(override));
// get followed channel
MOCK_METHOD(
void, getFollowedChannel,
(QString userID, QString broadcasterID,
ResultCallback<std::optional<HelixFollowedChannel>> successCallback,
FailureCallback<QString> failureCallback),
(override));
MOCK_METHOD(void, update, (QString clientId, QString oauthToken),
(override));

View file

@ -16,7 +16,7 @@ public:
MOCK_METHOD(void, addMessage,
(const QString &channelName, MessagePtr message,
const QString &platformName),
const QString &platformName, const QString &streamID),
(override));
};
@ -27,7 +27,8 @@ public:
~EmptyLogging() override = default;
void addMessage(const QString &channelName, MessagePtr message,
const QString &platformName) override
const QString &platformName,
const QString &streamID) override
{
//
}

View file

@ -2,13 +2,11 @@
#include "mocks/Channel.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/bttv/BttvLiveUpdates.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/seventv/eventapi/Client.hpp"
#include "providers/seventv/eventapi/Dispatch.hpp"
#include "providers/seventv/eventapi/Message.hpp"
#include "providers/seventv/SeventvEmotes.hpp"
#include "providers/seventv/SeventvEventAPI.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
namespace chatterino::mock {
@ -28,6 +26,38 @@ public:
{
}
void connect() override
{
}
void sendRawMessage(const QString &rawMessage) override
{
}
ChannelPtr getOrAddChannel(const QString &dirtyChannelName) override
{
assert(false && "unimplemented getOrAddChannel in mock irc server");
return {};
}
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName) override
{
assert(false && "unimplemented getChannelOrEmpty in mock irc server");
return {};
}
void addFakeMessage(const QString &data) override
{
}
void addGlobalSystemMessage(const QString &messageText) override
{
}
void forEachChannel(std::function<void(ChannelPtr)> func) override
{
}
void forEachChannelAndSpecialChannels(
std::function<void(ChannelPtr)> func) override
{
@ -46,16 +76,6 @@ public:
//
}
std::unique_ptr<BttvLiveUpdates> &getBTTVLiveUpdates() override
{
return this->bttvLiveUpdates;
}
std::unique_ptr<SeventvEventAPI> &getSeventvEventAPI() override
{
return this->seventvEventAPI;
}
const IndirectChannel &getWatchingChannel() const override
{
return this->watchingChannel;
@ -103,9 +123,6 @@ public:
ChannelPtr liveChannel;
ChannelPtr automodChannel;
QString lastUserThatWhisperedMe{"forsen"};
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
};
} // namespace chatterino::mock

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,7 @@
#pragma once
#include "common/Singleton.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "singletons/NativeMessaging.hpp"
#include <QApplication>
#include <cassert>
#include <memory>
@ -51,19 +47,24 @@ class ImageUploader;
class SeventvAPI;
class CrashHandler;
class BttvEmotes;
class BttvLiveUpdates;
class FfzEmotes;
class SeventvEmotes;
class SeventvEventAPI;
class ILinkResolver;
class IStreamerMode;
class IAbstractIrcServer;
class ITwitchUsers;
class IApplication
{
public:
IApplication();
virtual ~IApplication() = default;
virtual ~IApplication();
static IApplication *instance;
IApplication(const IApplication &) = delete;
IApplication(IApplication &&) = delete;
IApplication &operator=(const IApplication &) = delete;
IApplication &operator=(IApplication &&) = delete;
virtual bool isTest() const = 0;
@ -81,7 +82,6 @@ public:
virtual HighlightController *getHighlights() = 0;
virtual NotificationController *getNotifications() = 0;
virtual ITwitchIrcServer *getTwitch() = 0;
virtual IAbstractIrcServer *getTwitchAbstract() = 0;
virtual PubSub *getTwitchPubSub() = 0;
virtual ILogging *getChatLogger() = 0;
virtual IChatterinoBadges *getChatterinoBadges() = 0;
@ -98,23 +98,23 @@ public:
#endif
virtual Updates &getUpdates() = 0;
virtual BttvEmotes *getBttvEmotes() = 0;
virtual BttvLiveUpdates *getBttvLiveUpdates() = 0;
virtual FfzEmotes *getFfzEmotes() = 0;
virtual SeventvEmotes *getSeventvEmotes() = 0;
virtual SeventvEventAPI *getSeventvEventAPI() = 0;
virtual ILinkResolver *getLinkResolver() = 0;
virtual IStreamerMode *getStreamerMode() = 0;
virtual ITwitchUsers *getTwitchUsers() = 0;
};
class Application : public IApplication
{
const Paths &paths_;
const Args &args_;
std::vector<std::unique_ptr<Singleton>> singletons_;
int argc_{};
char **argv_{};
public:
static Application *instance;
Application(Settings &_settings, const Paths &paths, const Args &_args,
Updates &_updates);
~Application() override;
@ -129,51 +129,48 @@ public:
return false;
}
/**
* 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();
int run(QApplication &qtApp);
int run();
friend void test();
private:
Theme *const themes{};
std::unique_ptr<Fonts> fonts{};
Emotes *const emotes{};
AccountController *const accounts{};
HotkeyController *const hotkeys{};
WindowManager *const windows{};
Toasts *const toasts{};
ImageUploader *const imageUploader{};
SeventvAPI *const seventvAPI{};
CrashHandler *const crashHandler{};
CommandController *const commands{};
NotificationController *const notifications{};
HighlightController *const highlights{};
std::unique_ptr<Theme> themes;
std::unique_ptr<Fonts> fonts;
std::unique_ptr<Emotes> emotes;
std::unique_ptr<AccountController> accounts;
std::unique_ptr<HotkeyController> hotkeys;
std::unique_ptr<WindowManager> windows;
std::unique_ptr<Toasts> toasts;
std::unique_ptr<ImageUploader> imageUploader;
std::unique_ptr<SeventvAPI> seventvAPI;
std::unique_ptr<CrashHandler> crashHandler;
std::unique_ptr<CommandController> commands;
std::unique_ptr<NotificationController> notifications;
std::unique_ptr<HighlightController> highlights;
std::unique_ptr<TwitchIrcServer> twitch;
FfzBadges *const ffzBadges{};
SeventvBadges *const seventvBadges{};
std::unique_ptr<FfzBadges> ffzBadges;
std::unique_ptr<SeventvBadges> seventvBadges;
std::unique_ptr<UserDataController> userData;
std::unique_ptr<ISoundController> sound;
TwitchLiveController *const twitchLiveController{};
std::unique_ptr<TwitchLiveController> twitchLiveController;
std::unique_ptr<PubSub> twitchPubSub;
std::unique_ptr<TwitchBadges> twitchBadges;
std::unique_ptr<ChatterinoBadges> chatterinoBadges;
std::unique_ptr<BttvEmotes> bttvEmotes;
std::unique_ptr<BttvLiveUpdates> bttvLiveUpdates;
std::unique_ptr<FfzEmotes> ffzEmotes;
std::unique_ptr<SeventvEmotes> seventvEmotes;
std::unique_ptr<SeventvEventAPI> seventvEventAPI;
const std::unique_ptr<Logging> logging;
std::unique_ptr<ILinkResolver> linkResolver;
std::unique_ptr<IStreamerMode> streamerMode;
std::unique_ptr<ITwitchUsers> twitchUsers;
#ifdef CHATTERINO_HAVE_PLUGINS
PluginController *const plugins{};
std::unique_ptr<PluginController> plugins;
#endif
public:
@ -197,7 +194,6 @@ public:
NotificationController *getNotifications() override;
HighlightController *getHighlights() override;
ITwitchIrcServer *getTwitch() override;
IAbstractIrcServer *getTwitchAbstract() override;
PubSub *getTwitchPubSub() override;
ILogging *getChatLogger() override;
FfzBadges *getFfzBadges() override;
@ -212,51 +208,29 @@ public:
#ifdef CHATTERINO_HAVE_PLUGINS
PluginController *getPlugins() override;
#endif
Updates &getUpdates() override
{
assertInGuiThread();
return this->updates;
}
Updates &getUpdates() override;
BttvEmotes *getBttvEmotes() override;
BttvLiveUpdates *getBttvLiveUpdates() override;
FfzEmotes *getFfzEmotes() override;
SeventvEmotes *getSeventvEmotes() override;
SeventvEventAPI *getSeventvEventAPI() override;
ILinkResolver *getLinkResolver() override;
IStreamerMode *getStreamerMode() override;
ITwitchUsers *getTwitchUsers() override;
private:
void addSingleton(Singleton *singleton);
void initPubSub();
void initBttvLiveUpdates();
void initSeventvEventAPI();
void initNm(const Paths &paths);
template <typename T,
typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
T &emplace()
{
auto t = new T;
this->singletons_.push_back(std::unique_ptr<T>(t));
return *t;
}
template <typename T,
typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
T &emplace(T *t)
{
this->singletons_.push_back(std::unique_ptr<T>(t));
return *t;
}
NativeMessagingServer nmServer{};
NativeMessagingServer nmServer;
Updates &updates;
bool initialized{false};
};
Application *getApp();
// Get an interface version of the Application class - should be preferred when possible for new code
IApplication *getIApp();
IApplication *getApp();
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "BrowserExtension.hpp"
#include "singletons/NativeMessaging.hpp"
#include "util/RenameThread.hpp"
#include <iostream>
#include <memory>
@ -69,6 +70,7 @@ void runLoop()
std::this_thread::sleep_for(10s);
}
});
renameThread(thread, "BrowserPingCheck");
while (true)
{

View file

@ -276,12 +276,10 @@ set(SOURCE_FILES
messages/MessageColor.hpp
messages/MessageElement.cpp
messages/MessageElement.hpp
messages/MessageFlag.hpp
messages/MessageThread.cpp
messages/MessageThread.hpp
messages/SharedMessageBuilder.cpp
messages/SharedMessageBuilder.hpp
messages/layouts/MessageLayout.cpp
messages/layouts/MessageLayout.hpp
messages/layouts/MessageLayoutContainer.cpp
@ -338,22 +336,8 @@ set(SOURCE_FILES
providers/ffz/FfzUtil.cpp
providers/ffz/FfzUtil.hpp
providers/irc/AbstractIrcServer.cpp
providers/irc/AbstractIrcServer.hpp
providers/irc/Irc2.cpp
providers/irc/Irc2.hpp
providers/irc/IrcAccount.cpp
providers/irc/IrcAccount.hpp
providers/irc/IrcChannel2.cpp
providers/irc/IrcChannel2.hpp
providers/irc/IrcCommands.cpp
providers/irc/IrcCommands.hpp
providers/irc/IrcConnection2.cpp
providers/irc/IrcConnection2.hpp
providers/irc/IrcMessageBuilder.cpp
providers/irc/IrcMessageBuilder.hpp
providers/irc/IrcServer.cpp
providers/irc/IrcServer.hpp
providers/links/LinkInfo.cpp
providers/links/LinkInfo.hpp
@ -418,10 +402,10 @@ set(SOURCE_FILES
providers/twitch/TwitchHelpers.hpp
providers/twitch/TwitchIrcServer.cpp
providers/twitch/TwitchIrcServer.hpp
providers/twitch/TwitchMessageBuilder.cpp
providers/twitch/TwitchMessageBuilder.hpp
providers/twitch/TwitchUser.cpp
providers/twitch/TwitchUser.hpp
providers/twitch/TwitchUsers.cpp
providers/twitch/TwitchUsers.hpp
providers/twitch/pubsubmessages/AutoMod.cpp
providers/twitch/pubsubmessages/AutoMod.hpp
@ -444,8 +428,6 @@ set(SOURCE_FILES
providers/twitch/api/Helix.cpp
providers/twitch/api/Helix.hpp
singletons/Badges.cpp
singletons/Badges.hpp
singletons/CrashHandler.cpp
singletons/CrashHandler.hpp
singletons/Emotes.cpp
@ -487,11 +469,11 @@ set(SOURCE_FILES
util/ChannelHelpers.hpp
util/Clipboard.cpp
util/Clipboard.hpp
util/ConcurrentMap.hpp
util/DebugCount.cpp
util/DebugCount.hpp
util/DisplayBadge.cpp
util/DisplayBadge.hpp
util/Expected.hpp
util/FormatTime.cpp
util/FormatTime.hpp
util/FunctionEventFilter.cpp
@ -514,11 +496,12 @@ set(SOURCE_FILES
util/RapidjsonHelpers.hpp
util/RatelimitBucket.cpp
util/RatelimitBucket.hpp
util/RenameThread.cpp
util/RenameThread.hpp
util/SampleData.cpp
util/SampleData.hpp
util/SharedPtrElementLess.hpp
util/SplitCommand.cpp
util/SplitCommand.hpp
util/SignalListener.hpp
util/StreamLink.cpp
util/StreamLink.hpp
util/ThreadGuard.hpp
@ -578,15 +561,10 @@ set(SOURCE_FILES
widgets/dialogs/EditHotkeyDialog.hpp
widgets/dialogs/EmotePopup.cpp
widgets/dialogs/EmotePopup.hpp
widgets/dialogs/IrcConnectionEditor.cpp
widgets/dialogs/IrcConnectionEditor.hpp
widgets/dialogs/IrcConnectionEditor.ui
widgets/dialogs/LastRunCrashDialog.cpp
widgets/dialogs/LastRunCrashDialog.hpp
widgets/dialogs/LoginDialog.cpp
widgets/dialogs/LoginDialog.hpp
widgets/dialogs/NotificationPopup.cpp
widgets/dialogs/NotificationPopup.hpp
widgets/dialogs/QualityPopup.cpp
widgets/dialogs/QualityPopup.hpp
widgets/dialogs/ReplyThreadPopup.cpp
@ -645,6 +623,8 @@ set(SOURCE_FILES
widgets/helper/IconDelegate.hpp
widgets/helper/InvisibleSizeGrip.cpp
widgets/helper/InvisibleSizeGrip.hpp
widgets/helper/MessageView.cpp
widgets/helper/MessageView.hpp
widgets/helper/NotebookButton.cpp
widgets/helper/NotebookButton.hpp
widgets/helper/NotebookTab.cpp
@ -949,6 +929,10 @@ set_target_properties(${LIBRARY_PROJECT}
# this is its own project.
set(VERSION_SOURCE_FILES common/Version.cpp common/Version.hpp)
add_library(${VERSION_PROJECT} STATIC ${VERSION_SOURCE_FILES})
target_compile_definitions(${VERSION_PROJECT} PRIVATE
$<$<BOOL:${WIN32}>:USEWINSDK>
$<$<BOOL:${BUILD_WITH_CRASHPAD}>:CHATTERINO_WITH_CRASHPAD>
)
# source group for IDEs
source_group(TREE ${CMAKE_SOURCE_DIR} FILES ${VERSION_SOURCE_FILES})

View file

@ -6,6 +6,7 @@
# include <IrcCommand>
# include <IrcConnection>
# include <IrcMessage>
# include <nonstd/expected.hpp>
# include <pajlada/serialize.hpp>
# include <pajlada/settings/setting.hpp>
# include <pajlada/settings/settinglistener.hpp>
@ -108,6 +109,7 @@
# include <cinttypes>
# include <climits>
# include <cmath>
# include <concepts>
# include <cstdint>
# include <ctime>
# include <functional>

View file

@ -41,7 +41,7 @@ namespace {
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
auto dark = qApp->palette();
auto dark = QApplication::palette();
dark.setColor(QPalette::Window, QColor(22, 22, 22));
dark.setColor(QPalette::WindowText, Qt::white);
@ -49,7 +49,7 @@ namespace {
dark.setColor(QPalette::Base, QColor("#333"));
dark.setColor(QPalette::AlternateBase, QColor("#444"));
dark.setColor(QPalette::ToolTipBase, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::black);
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::Button, QColor(70, 70, 70));
@ -71,7 +71,7 @@ namespace {
dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
qApp->setPalette(dark);
QApplication::setPalette(dark);
}
void initQt()
@ -131,7 +131,7 @@ namespace {
using namespace std::chrono_literals;
if (std::chrono::steady_clock::now() - signalsInitTime > 30s &&
getIApp()->getCrashHandler()->shouldRecover())
getApp()->getCrashHandler()->shouldRecover())
{
QProcess proc;
@ -241,22 +241,7 @@ void runGui(QApplication &a, const Paths &paths, Settings &settings,
}
#endif
auto thread = std::thread([dir = paths.miscDirectory] {
{
auto path = combinePath(dir, "Update.exe");
if (QFile::exists(path))
{
QFile::remove(path);
}
}
{
auto path = combinePath(dir, "update.zip");
if (QFile::exists(path))
{
QFile::remove(path);
}
}
});
updates.deleteOldFiles();
// Clear the cache 1 minute after start.
QTimer::singleShot(60 * 1000, [cachePath = paths.cacheDirectory(),
@ -278,13 +263,10 @@ void runGui(QApplication &a, const Paths &paths, Settings &settings,
Application app(settings, paths, args, updates);
app.initialize(settings, paths);
app.run(a);
app.run();
app.save();
if (!args.dontSaveSettings)
{
pajlada::Settings::SettingManager::gSave();
}
settings.requestSave();
chatterino::NetworkManager::deinit();
@ -292,9 +274,6 @@ void runGui(QApplication &a, const Paths &paths, Settings &settings,
// flushing windows clipboard to keep copied messages
flushClipboard();
#endif
app.fakeDtor();
_exit(0);
}
} // namespace chatterino

View file

@ -1,10 +1,12 @@
#pragma once
#include <boost/container_hash/hash_fwd.hpp>
#include <QHash>
#include <QString>
#include <functional>
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define QStringAlias(name) \
namespace chatterino { \
struct name { \
@ -27,12 +29,22 @@
return qHash(s.string); \
} \
}; \
} /* namespace std */
} /* namespace std */ \
namespace boost { \
template <> \
struct hash<chatterino::name> { \
std::size_t operator()(chatterino::name const &s) const \
{ \
return qHash(s.string); \
} \
}; \
} /* namespace boost */
QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);
QStringAlias(EmoteId);
QStringAlias(EmoteSetId);
QStringAlias(EmoteName);
QStringAlias(EmoteAuthor);

View file

@ -3,8 +3,6 @@
#include "Application.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/irc/IrcChannel2.hpp"
#include "providers/irc/IrcServer.hpp"
#include "providers/twitch/IrcMessageHandler.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Logging.hpp"
@ -26,12 +24,16 @@ namespace chatterino {
// Channel
//
Channel::Channel(const QString &name, Type type)
: completionModel(*this, nullptr)
: completionModel(new TabCompletionModel(*this, nullptr))
, lastDate_(QDate::currentDate())
, name_(name)
, messages_(getSettings()->scrollbackSplitLimit)
, type_(type)
{
if (this->isTwitchChannel())
{
this->platform_ = "twitch";
}
}
Channel::~Channel()
@ -79,37 +81,25 @@ LimitedQueueSnapshot<MessagePtr> Channel::getMessageSnapshot()
return this->messages_.getSnapshot();
}
void Channel::addMessage(MessagePtr message,
void Channel::addMessage(MessagePtr message, MessageContext context,
std::optional<MessageFlags> overridingFlags)
{
MessagePtr deleted;
if (!overridingFlags || !overridingFlags->has(MessageFlag::DoNotLog))
if (context == MessageContext::Original)
{
QString channelPlatform("other");
if (this->type_ == Type::Irc)
// Only log original messages
auto isDoNotLogSet =
(overridingFlags && overridingFlags->has(MessageFlag::DoNotLog)) ||
message->flags.has(MessageFlag::DoNotLog);
if (!isDoNotLogSet)
{
auto *irc = dynamic_cast<IrcChannel *>(this);
if (irc != nullptr)
{
auto *ircServer = irc->server();
if (ircServer != nullptr)
{
channelPlatform = QString("irc-%1").arg(
irc->server()->userFriendlyIdentifier());
// Only log messages where the `DoNotLog` flag is not set
getApp()->getChatLogger()->addMessage(this->name_, message,
this->platform_,
this->getCurrentStreamID());
}
else
{
channelPlatform = "irc-unknown";
}
}
}
else if (this->isTwitchChannel())
{
channelPlatform = "twitch";
}
getIApp()->getChatLogger()->addMessage(this->name_, message,
channelPlatform);
}
if (this->messages_.pushBack(message, deleted))
@ -120,6 +110,12 @@ void Channel::addMessage(MessagePtr message,
this->messageAppended.invoke(message, overridingFlags);
}
void Channel::addSystemMessage(const QString &contents)
{
auto msg = makeSystemMessage(contents);
this->addMessage(msg, MessageContext::Original);
}
void Channel::addOrReplaceTimeout(MessagePtr message)
{
addOrReplaceChannelTimeout(
@ -128,7 +124,7 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
this->replaceMessage(msg, replacement);
},
[this](auto msg) {
this->addMessage(msg);
this->addMessage(msg, MessageContext::Original);
},
true);
@ -279,6 +275,12 @@ void Channel::deleteMessage(QString messageID)
}
}
void Channel::clearMessages()
{
this->messages_.clear();
this->messagesCleared.invoke();
}
MessagePtr Channel::findMessage(QString messageID)
{
MessagePtr res;
@ -357,6 +359,11 @@ void Channel::reconnect()
{
}
QString Channel::getCurrentStreamID() const
{
return {};
}
std::shared_ptr<Channel> Channel::getEmpty()
{
static std::shared_ptr<Channel> channel(new Channel("", Type::None));

View file

@ -1,8 +1,8 @@
#pragma once
#include "common/FlagsEnum.hpp"
#include "controllers/completion/TabCompletionModel.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/MessageFlag.hpp"
#include <magic_enum/magic_enum.hpp>
#include <pajlada/signals/signal.hpp>
@ -17,8 +17,6 @@ namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
enum class MessageFlag : int64_t;
using MessageFlags = FlagsEnum<MessageFlag>;
enum class TimeoutStackStyle : int {
StackHard = 0,
@ -28,6 +26,14 @@ enum class TimeoutStackStyle : int {
Default = DontStackBeyondUserMessage,
};
/// Context of the message being added to a channel
enum class MessageContext {
/// This message is the original
Original,
/// This message is a repost of a message that has already been added in a channel
Repost,
};
class Channel : public std::enable_shared_from_this<Channel>
{
public:
@ -45,7 +51,6 @@ public:
TwitchLive,
TwitchAutomod,
TwitchEnd,
Irc,
Misc,
};
@ -66,6 +71,7 @@ public:
pajlada::Signals::Signal<const std::vector<MessagePtr> &> filledInMessages;
pajlada::Signals::NoArgSignal destroyed;
pajlada::Signals::NoArgSignal displayNameChanged;
pajlada::Signals::NoArgSignal messagesCleared;
Type getType() const;
const QString &getName() const;
@ -79,10 +85,12 @@ public:
// overridingFlags can be filled in with flags that should be used instead
// of the message's flags. This is useful in case a flag is specific to a
// type of split
void addMessage(MessagePtr message,
void addMessage(MessagePtr message, MessageContext context,
std::optional<MessageFlags> overridingFlags = std::nullopt);
void addMessagesAtStart(const std::vector<MessagePtr> &messages_);
void addSystemMessage(const QString &contents);
/// Inserts the given messages in order by Message::serverReceivedTime.
void fillInMissingMessages(const std::vector<MessagePtr> &messages);
@ -92,6 +100,9 @@ public:
void replaceMessage(size_t index, MessagePtr replacement);
void deleteMessage(QString messageID);
/// Removes all messages from this channel and invokes #messagesCleared
void clearMessages();
MessagePtr findMessage(QString messageID);
bool hasMessages() const;
@ -109,15 +120,17 @@ public:
virtual bool shouldIgnoreHighlights() const;
virtual bool canReconnect() const;
virtual void reconnect();
virtual QString getCurrentStreamID() const;
static std::shared_ptr<Channel> getEmpty();
TabCompletionModel completionModel;
TabCompletionModel *completionModel;
QDate lastDate_;
protected:
virtual void onConnected();
virtual void messageRemovedFromStart(const MessagePtr &msg);
QString platform_{"other"};
private:
const QString name_;
@ -173,8 +186,6 @@ constexpr magic_enum::customize::customize_t
return "live";
case Type::TwitchAutomod:
return "automod";
case Type::Irc:
return "irc";
case Type::Misc:
return "misc";
default:

View file

@ -1,9 +1,8 @@
#include "ChannelChatters.hpp"
#include "common/ChannelChatters.hpp"
#include "common/Channel.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include <QColor>
@ -39,11 +38,11 @@ void ChannelChatters::addJoinedUser(const QString &user)
auto joinedUsers = this->joinedUsers_.access();
joinedUsers->sort();
MessageBuilder builder;
TwitchMessageBuilder::listOfUsersSystemMessage(
"Users joined:", *joinedUsers, &this->channel_, &builder);
builder->flags.set(MessageFlag::Collapsed);
this->channel_.addMessage(builder.release());
this->channel_.addMessage(
MessageBuilder::makeListOfUsersMessage(
"Users joined:", *joinedUsers, &this->channel_,
{MessageFlag::Collapsed}),
MessageContext::Original);
joinedUsers->clear();
this->joinedUsersMergeQueued_ = false;
@ -64,11 +63,11 @@ void ChannelChatters::addPartedUser(const QString &user)
auto partedUsers = this->partedUsers_.access();
partedUsers->sort();
MessageBuilder builder;
TwitchMessageBuilder::listOfUsersSystemMessage(
"Users parted:", *partedUsers, &this->channel_, &builder);
builder->flags.set(MessageFlag::Collapsed);
this->channel_.addMessage(builder.release());
this->channel_.addMessage(
MessageBuilder::makeListOfUsersMessage(
"Users parted:", *partedUsers, &this->channel_,
{MessageFlag::Collapsed}),
MessageContext::Original);
partedUsers->clear();
this->partedUsersMergeQueued_ = false;

View file

@ -2,12 +2,10 @@
#include "debug/Benchmark.hpp"
#include <tuple>
namespace chatterino {
ChatterSet::ChatterSet()
: items(chatterLimit)
: items(ChatterSet::CHATTER_LIMIT)
{
}
@ -22,7 +20,7 @@ void ChatterSet::updateOnlineChatters(
BenchmarkGuard bench("update online chatters");
// Create a new lru cache without the users that are not present anymore.
cache::lru_cache<QString, QString> tmp(chatterLimit);
cache::lru_cache<QString, QString> tmp(ChatterSet::CHATTER_LIMIT);
for (auto &&chatter : lowerCaseUsernames)
{
@ -32,7 +30,7 @@ void ChatterSet::updateOnlineChatters(
// Less chatters than the limit => try to preserve as many as possible.
}
else if (lowerCaseUsernames.size() < chatterLimit)
else if (lowerCaseUsernames.size() < ChatterSet::CHATTER_LIMIT)
{
tmp.put(chatter, chatter);
}

View file

@ -5,9 +5,6 @@
#include <lrucache/lrucache.hpp>
#include <QString>
#include <functional>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -19,7 +16,7 @@ class ChatterSet
{
public:
/// The limit of how many chatters can be saved for a channel.
static constexpr size_t chatterLimit = 2000;
static constexpr size_t CHATTER_LIMIT = 2000;
ChatterSet();

View file

@ -141,4 +141,14 @@ public:
using pajlada::Settings::Setting<QString>::operator QString;
};
template <typename T>
struct IsChatterinoSettingT : std::false_type {
};
template <typename T>
struct IsChatterinoSettingT<ChatterinoSetting<T>> : std::true_type {
};
template <typename T>
concept IsChatterinoSetting = IsChatterinoSettingT<T>::value;
} // namespace chatterino

View file

@ -5,17 +5,17 @@
#include <QWidget>
#include <memory>
#include <optional>
#include <string>
#define LINK_CHATTERINO_WIKI "https://wiki.chatterino.com"
#define LINK_CHATTERINO_DISCORD "https://discord.gg/7Y5AYhAK4z"
#define LINK_CHATTERINO_SOURCE "https://github.com/Chatterino/chatterino2"
namespace chatterino {
const inline auto TWITCH_PLAYER_URL =
QStringLiteral("https://player.twitch.tv/?channel=%1&parent=twitch.tv");
constexpr QStringView LINK_CHATTERINO_WIKI = u"https://wiki.chatterino.com";
constexpr QStringView LINK_CHATTERINO_DISCORD =
u"https://discord.gg/7Y5AYhAK4z";
constexpr QStringView LINK_CHATTERINO_SOURCE =
u"https://github.com/Chatterino/chatterino2";
constexpr QStringView TWITCH_PLAYER_URL =
u"https://player.twitch.tv/?channel=%1&parent=twitch.tv";
enum class HighlightState {
None,
@ -23,21 +23,14 @@ enum class HighlightState {
NewMessage,
};
const Qt::KeyboardModifiers showSplitOverlayModifiers =
constexpr Qt::KeyboardModifiers SHOW_SPLIT_OVERLAY_MODIFIERS =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions =
constexpr Qt::KeyboardModifiers SHOW_ADD_SPLIT_REGIONS =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
constexpr Qt::KeyboardModifiers SHOW_RESIZE_HANDLES_MODIFIERS =
Qt::ControlModifier;
#ifndef ATTR_UNUSED
# ifdef Q_OS_WIN
# define ATTR_UNUSED
# else
# define ATTR_UNUSED __attribute__((unused))
# endif
#endif
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
constexpr const char *ANONYMOUS_USERNAME_LABEL = " - anonymous - ";
template <typename T>
std::weak_ptr<T> weakOf(T *element)

View file

@ -1,102 +0,0 @@
#pragma once
#include <QMap>
#include <QMutex>
#include <QMutexLocker>
#include <functional>
#include <map>
#include <memory>
namespace chatterino {
template <typename TKey, typename TValue>
class ConcurrentMap
{
public:
ConcurrentMap() = default;
bool tryGet(const TKey &name, TValue &value) const
{
QMutexLocker lock(&this->mutex);
auto a = this->data.find(name);
if (a == this->data.end())
{
return false;
}
value = a.value();
return true;
}
TValue getOrAdd(const TKey &name, std::function<TValue()> addLambda)
{
QMutexLocker lock(&this->mutex);
auto a = this->data.find(name);
if (a == this->data.end())
{
TValue value = addLambda();
this->data.insert(name, value);
return value;
}
return a.value();
}
TValue &operator[](const TKey &name)
{
QMutexLocker lock(&this->mutex);
return this->data[name];
}
void clear()
{
QMutexLocker lock(&this->mutex);
this->data.clear();
}
void insert(const TKey &name, const TValue &value)
{
QMutexLocker lock(&this->mutex);
this->data.insert(name, value);
}
void each(
std::function<void(const TKey &name, const TValue &value)> func) const
{
QMutexLocker lock(&this->mutex);
QMapIterator<TKey, TValue> it(this->data);
while (it.hasNext())
{
it.next();
func(it.key(), it.value());
}
}
void each(std::function<void(const TKey &name, TValue &value)> func)
{
QMutexLocker lock(&this->mutex);
QMutableMapIterator<TKey, TValue> it(this->data);
while (it.hasNext())
{
it.next();
func(it.key(), it.value());
}
}
private:
mutable QMutex mutex;
QMap<TKey, TValue> data;
};
} // namespace chatterino

View file

@ -6,12 +6,13 @@
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
#include "util/CombinePath.hpp"
#include "util/Overloaded.hpp"
#include "util/Variant.hpp"
#include <QApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSaveFile>
#include <QStringBuilder>
#include <variant>
@ -27,16 +28,16 @@
# endif
#endif
#define FORMAT_NAME \
([&] { \
assert(!provider.contains(":")); \
return QString("chatterino:%1:%2").arg(provider).arg(name_); \
})()
namespace {
using namespace chatterino;
QString formatName(const QString &provider, const QString &name)
{
assert(!provider.contains(":"));
return u"chatterino:" % provider % u':' % name;
}
bool useKeyring()
{
#ifdef NO_QTKEYCHAIN
@ -57,7 +58,7 @@ bool useKeyring()
// Insecure storage:
QString insecurePath()
{
return combinePath(getIApp()->getPaths().settingsDirectory,
return combinePath(getApp()->getPaths().settingsDirectory,
"credentials.json");
}
@ -89,7 +90,7 @@ void queueInsecureSave()
if (!isQueued)
{
isQueued = true;
QTimer::singleShot(200, qApp, [] {
QTimer::singleShot(200, QApplication::instance(), [] {
storeInsecure(insecureInstance());
isQueued = false;
});
@ -133,8 +134,8 @@ void runNextJob()
job->setAutoDelete(true);
job->setKey(set.name);
job->setTextData(set.credential);
QObject::connect(job, &QKeychain::Job::finished, qApp,
[](auto) {
QObject::connect(job, &QKeychain::Job::finished,
QApplication::instance(), [](auto) {
runNextJob();
});
job->start();
@ -143,8 +144,8 @@ void runNextJob()
auto *job = new QKeychain::DeletePasswordJob("chatterino");
job->setAutoDelete(true);
job->setKey(erase.name);
QObject::connect(job, &QKeychain::Job::finished, qApp,
[](auto) {
QObject::connect(job, &QKeychain::Job::finished,
QApplication::instance(), [](auto) {
runNextJob();
});
job->start();
@ -185,7 +186,7 @@ void Credentials::get(const QString &provider, const QString &name_,
{
assertInGuiThread();
auto name = FORMAT_NAME;
auto name = formatName(provider, name_);
if (useKeyring())
{
@ -220,7 +221,7 @@ void Credentials::set(const QString &provider, const QString &name_,
/// On linux, we try to use a keychain but show a message to disable it when it fails.
/// XXX: add said message
auto name = FORMAT_NAME;
auto name = formatName(provider, name_);
if (useKeyring())
{
@ -243,7 +244,7 @@ void Credentials::erase(const QString &provider, const QString &name_)
{
assertInGuiThread();
auto name = FORMAT_NAME;
auto name = formatName(provider, name_);
if (useKeyring())
{

View file

@ -1,59 +1,74 @@
#pragma once
#include <initializer_list>
#include <concepts>
#include <type_traits>
namespace chatterino {
template <typename T, typename Q = typename std::underlying_type<T>::type>
template <typename T>
requires std::is_enum_v<T>
class FlagsEnum
{
public:
FlagsEnum()
: value_(static_cast<T>(0))
using Int = std::underlying_type_t<T>;
constexpr FlagsEnum() noexcept = default;
constexpr FlagsEnum(std::convertible_to<T> auto... flags) noexcept
: value_(
static_cast<T>((static_cast<Int>(static_cast<T>(flags)) | ...)))
{
}
FlagsEnum(T value)
: value_(value)
friend constexpr bool operator==(FlagsEnum lhs, FlagsEnum rhs) noexcept
{
return lhs.value_ == rhs.value_;
}
friend constexpr bool operator!=(FlagsEnum lhs, FlagsEnum rhs) noexcept
{
return lhs.value_ != rhs.value_;
}
FlagsEnum(std::initializer_list<T> flags)
friend constexpr bool operator==(FlagsEnum lhs, T rhs) noexcept
{
for (auto flag : flags)
{
this->set(flag);
return lhs.value_ == rhs;
}
friend constexpr bool operator!=(FlagsEnum lhs, T rhs) noexcept
{
return lhs.value_ != rhs;
}
bool operator==(const FlagsEnum<T> &other) const
friend constexpr bool operator==(T lhs, FlagsEnum rhs) noexcept
{
return this->value_ == other.value_;
return lhs == rhs.value_;
}
friend constexpr bool operator!=(T lhs, FlagsEnum rhs) noexcept
{
return lhs != rhs.value_;
}
bool operator!=(const FlagsEnum<T> &other) const
constexpr void set(std::convertible_to<T> auto... flags) noexcept
{
return this->value_ != other.value_;
}
void set(T flag)
{
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
this->value_ =
static_cast<T>(static_cast<Int>(this->value_) |
(static_cast<Int>(static_cast<T>(flags)) | ...));
}
/** Adds the flags from `flags` in this enum. */
void set(FlagsEnum flags)
constexpr void set(FlagsEnum flags) noexcept
{
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flags.value_);
this->value_ = static_cast<T>(static_cast<Int>(this->value_) |
static_cast<Int>(flags.value_));
}
void unset(T flag)
constexpr void unset(std::convertible_to<T> auto... flags) noexcept
{
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
this->value_ =
static_cast<T>(static_cast<Int>(this->value_) &
~(static_cast<Int>(static_cast<T>(flags)) | ...));
}
void set(T flag, bool value)
constexpr void set(T flag, bool value) noexcept
{
if (value)
{
@ -65,43 +80,65 @@ public:
}
}
bool has(T flag) const
constexpr FlagsEnum operator|(T flag) const noexcept
{
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
return static_cast<T>(static_cast<Int>(this->value_) |
static_cast<Int>(flag));
}
FlagsEnum operator|(T flag)
constexpr FlagsEnum operator|(FlagsEnum rhs) const noexcept
{
FlagsEnum xd;
xd.value_ = this->value_;
xd.set(flag, true);
return xd;
return static_cast<T>(static_cast<Int>(this->value_) |
static_cast<Int>(rhs.value_));
}
FlagsEnum operator|(FlagsEnum rhs)
constexpr bool has(T flag) const noexcept
{
return static_cast<T>(static_cast<Q>(this->value_) |
static_cast<Q>(rhs.value_));
return static_cast<Int>(this->value_) & static_cast<Int>(flag);
}
bool hasAny(FlagsEnum flags) const
constexpr bool hasAny(FlagsEnum flags) const noexcept
{
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
return (static_cast<Int>(this->value_) &
static_cast<Int>(flags.value_)) != 0;
}
bool hasAll(FlagsEnum<T> flags) const
constexpr bool hasAny(std::convertible_to<T> auto... flags) const noexcept
{
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value);
return this->hasAny(FlagsEnum{flags...});
}
bool hasNone(std::initializer_list<T> flags) const
constexpr bool hasAll(FlagsEnum flags) const noexcept
{
return !this->hasAny(flags);
return (static_cast<Int>(this->value_) &
static_cast<Int>(flags.value_)) ==
static_cast<Int>(flags.value_);
}
T value() const
constexpr bool hasAll(std::convertible_to<T> auto... flags) const noexcept
{
return this->hasAll(FlagsEnum{flags...});
}
constexpr bool hasNone(FlagsEnum flags) const noexcept
{
return (static_cast<Int>(this->value_) &
static_cast<Int>(flags.value_)) == 0;
}
constexpr bool hasNone() const noexcept = delete;
constexpr bool hasNone(std::convertible_to<T> auto... flags) const noexcept
{
return this->hasNone(FlagsEnum{flags...});
}
/// Returns true if the enum has no flag set (i.e. its underlying value is 0)
constexpr bool isEmpty() const noexcept
{
return static_cast<Int>(this->value_) == 0;
}
constexpr T value() const noexcept
{
return this->value_;
}

View file

@ -1,62 +0,0 @@
#pragma once
#include <QColor>
#include <QMap>
namespace chatterino {
// Colors taken from https://modern.ircdocs.horse/formatting.html
static QMap<int, QColor> IRC_COLORS = {
{0, QColor("white")}, {1, QColor("black")},
{2, QColor("blue")}, {3, QColor("green")},
{4, QColor("red")}, {5, QColor("brown")},
{6, QColor("purple")}, {7, QColor("orange")},
{8, QColor("yellow")}, {9, QColor("lightgreen")},
{10, QColor("cyan")}, {11, QColor("lightcyan")},
{12, QColor("lightblue")}, {13, QColor("pink")},
{14, QColor("gray")}, {15, QColor("lightgray")},
{16, QColor("#470000")}, {17, QColor("#472100")},
{18, QColor("#474700")}, {19, QColor("#324700")},
{20, QColor("#004700")}, {21, QColor("#00472c")},
{22, QColor("#004747")}, {23, QColor("#002747")},
{24, QColor("#000047")}, {25, QColor("#2e0047")},
{26, QColor("#470047")}, {27, QColor("#47002a")},
{28, QColor("#740000")}, {29, QColor("#743a00")},
{30, QColor("#747400")}, {31, QColor("#517400")},
{32, QColor("#007400")}, {33, QColor("#007449")},
{34, QColor("#007474")}, {35, QColor("#004074")},
{36, QColor("#000074")}, {37, QColor("#4b0074")},
{38, QColor("#740074")}, {39, QColor("#740045")},
{40, QColor("#b50000")}, {41, QColor("#b56300")},
{42, QColor("#b5b500")}, {43, QColor("#7db500")},
{44, QColor("#00b500")}, {45, QColor("#00b571")},
{46, QColor("#00b5b5")}, {47, QColor("#0063b5")},
{48, QColor("#0000b5")}, {49, QColor("#7500b5")},
{50, QColor("#b500b5")}, {51, QColor("#b5006b")},
{52, QColor("#ff0000")}, {53, QColor("#ff8c00")},
{54, QColor("#ffff00")}, {55, QColor("#b2ff00")},
{56, QColor("#00ff00")}, {57, QColor("#00ffa0")},
{58, QColor("#00ffff")}, {59, QColor("#008cff")},
{60, QColor("#0000ff")}, {61, QColor("#a500ff")},
{62, QColor("#ff00ff")}, {63, QColor("#ff0098")},
{64, QColor("#ff5959")}, {65, QColor("#ffb459")},
{66, QColor("#ffff71")}, {67, QColor("#cfff60")},
{68, QColor("#6fff6f")}, {69, QColor("#65ffc9")},
{70, QColor("#6dffff")}, {71, QColor("#59b4ff")},
{72, QColor("#5959ff")}, {73, QColor("#c459ff")},
{74, QColor("#ff66ff")}, {75, QColor("#ff59bc")},
{76, QColor("#ff9c9c")}, {77, QColor("#ffd39c")},
{78, QColor("#ffff9c")}, {79, QColor("#e2ff9c")},
{80, QColor("#9cff9c")}, {81, QColor("#9cffdb")},
{82, QColor("#9cffff")}, {83, QColor("#9cd3ff")},
{84, QColor("#9c9cff")}, {85, QColor("#dc9cff")},
{86, QColor("#ff9cff")}, {87, QColor("#ff94d3")},
{88, QColor("#000000")}, {89, QColor("#131313")},
{90, QColor("#282828")}, {91, QColor("#363636")},
{92, QColor("#4d4d4d")}, {93, QColor("#656565")},
{94, QColor("#818181")}, {95, QColor("#9f9f9f")},
{96, QColor("#bcbcbc")}, {97, QColor("#e2e2e2")},
{98, QColor("#ffffff")},
};
} // namespace chatterino

View file

@ -1,17 +1,24 @@
#define QT_NO_CAST_FROM_ASCII // avoids unexpected implicit casts
#include "common/LinkParser.hpp"
#include "util/QCompareCaseInsensitive.hpp"
#include <QFile>
#include <QSet>
#include <QString>
#include <QStringView>
#include <QTextStream>
#include <set>
namespace {
QSet<QString> &tlds()
using namespace chatterino;
using TldSet = std::set<QString, QCompareCaseInsensitive>;
TldSet &tlds()
{
static QSet<QString> tlds = [] {
static TldSet tlds = [] {
QFile file(QStringLiteral(":/tlds.txt"));
file.open(QFile::ReadOnly);
QTextStream stream(&file);
@ -21,19 +28,12 @@ QSet<QString> &tlds()
#else
stream.setCodec("UTF-8");
#endif
int safetyMax = 20000;
QSet<QString> set;
TldSet set;
while (!stream.atEnd())
{
auto line = stream.readLine();
set.insert(line);
if (safetyMax-- == 0)
{
break;
}
set.emplace(stream.readLine());
}
return set;
@ -43,7 +43,7 @@ QSet<QString> &tlds()
bool isValidTld(QStringView tld)
{
return tlds().contains(tld.toString().toLower());
return tlds().contains(tld);
}
bool isValidIpv4(QStringView host)
@ -113,19 +113,72 @@ bool startsWithPort(QStringView string)
return true;
}
/// @brief Strips ignored characters off @a source
///
/// As per https://github.github.com/gfm/#autolinks-extension-:
///
/// '<', '*', '_', '~', and '(' are ignored at the beginning
/// '>', '?', '!', '.', ',', ':', '*', '~', and ')' are ignored at the end.
///
/// A difference to GFM is that '_' isn't a valid suffix.
///
/// This might remove more than desired (e.g. "(a.com/(foo))" -> "a.com/(foo").
/// Parentheses are counted after recognizing a valid IP/host.
void strip(QStringView &source)
{
while (!source.isEmpty())
{
auto c = source.first();
if (c == u'<' || c == u'*' || c == u'_' || c == u'~' || c == u'(')
{
source = source.mid(1);
continue;
}
break;
}
while (!source.isEmpty())
{
auto c = source.last();
if (c == u'>' || c == u'?' || c == u'!' || c == u'.' || c == u',' ||
c == u':' || c == u'*' || c == u'~' || c == u')')
{
source.chop(1);
continue;
}
break;
}
}
/// @brief Checks if @a c is valid in a domain
///
/// Valid characters are 0-9, A-Z, a-z, '-', '_', and '.' (like in GFM)
/// and all non-ASCII characters (unlike in GFM).
Q_ALWAYS_INLINE bool isValidDomainChar(char16_t c)
{
return c >= 0x80 || (u'0' <= c && c <= u'9') || (u'A' <= c && c <= u'Z') ||
(u'a' <= c && c <= u'z') || c == u'_' || c == u'-' || c == u'.';
}
} // namespace
namespace chatterino {
namespace chatterino::linkparser {
LinkParser::LinkParser(const QString &unparsedString)
std::optional<Parsed> parse(const QString &source) noexcept
{
ParsedLink result;
using SizeType = QString::size_type;
std::optional<Parsed> result;
// This is not implemented with a regex to increase performance.
QStringView remaining(unparsedString);
QStringView protocol(remaining);
QStringView link{source};
strip(link);
QStringView remaining = link;
QStringView protocol;
// Check protocol for https?://
if (remaining.startsWith(QStringLiteral("http"), Qt::CaseInsensitive) &&
if (remaining.startsWith(u"http", Qt::CaseInsensitive) &&
remaining.length() >= 4 + 3 + 1) // 'http' + '://' + [any]
{
// optimistic view assuming there's a protocol (http or https)
@ -136,11 +189,11 @@ LinkParser::LinkParser(const QString &unparsedString)
withProto = withProto.mid(1);
}
if (withProto.startsWith(QStringLiteral("://")))
if (withProto.startsWith(u"://"))
{
// there's really a protocol => consume it
remaining = withProto.mid(3);
result.protocol = {protocol.begin(), remaining.begin()};
protocol = {link.begin(), remaining.begin()};
}
}
@ -150,18 +203,18 @@ LinkParser::LinkParser(const QString &unparsedString)
QStringView host = remaining;
QStringView rest;
bool lastWasDot = true;
int lastDotPos = -1;
int nDots = 0;
SizeType lastDotPos = -1;
SizeType nDots = 0;
// Extract the host
for (int i = 0; i < remaining.size(); i++)
for (SizeType i = 0; i < remaining.size(); i++)
{
char16_t currentChar = remaining[i].unicode();
if (currentChar == u'.')
{
if (lastWasDot) // no double dots ..
{
return;
return result;
}
lastDotPos = i;
lastWasDot = true;
@ -181,7 +234,7 @@ LinkParser::LinkParser(const QString &unparsedString)
if (!startsWithPort(remaining))
{
return;
return result;
}
break;
@ -194,27 +247,68 @@ LinkParser::LinkParser(const QString &unparsedString)
rest = remaining.mid(i);
break;
}
if (!isValidDomainChar(currentChar))
{
return result;
}
}
if (lastWasDot || lastDotPos <= 0)
{
return;
return result;
}
// check host/tld
if ((nDots == 3 && isValidIpv4(host)) ||
isValidTld(host.mid(lastDotPos + 1)))
{
result.host = host;
result.rest = rest;
result.source = unparsedString;
this->result_ = std::move(result);
// scan for parentheses (only if there were characters excluded)
if (link.end() != source.end() && !rest.empty())
{
size_t nestingLevel = 0;
// position after the last closing brace (i.e. the minimum characters to include)
const auto *lastClose = link.end();
// scan source from rest until the end:
// lastClose
// v
// (example.com/foo/bar/#baz_(qox)),
// ▏╌╌rest (before)╌ ▏
// ▏╌╌╌╌╌╌╌link (before)╌╌╌╌╌╌╌ ▏
// ▏╌╌rest (after)╌╌╌ ▏
// ▏╌╌╌╌╌╌╌link (after)╌╌╌╌╌╌╌╌╌ ▏
// ▏╌╌╌╌╌╌╌╌╌╌╌╌╌source╌╌╌╌╌╌╌╌╌╌╌╌ ▏
// ▏╌╌╌╌╌╌╌search╌╌╌╌╌╌ ▏
for (const auto *it = rest.begin(); it < source.end(); it++)
{
if (it->unicode() == u'(')
{
nestingLevel++;
continue;
}
if (nestingLevel != 0 && it->unicode() == u')')
{
nestingLevel--;
if (nestingLevel == 0)
{
lastClose = it + 1;
}
}
}
link = QStringView{link.begin(), std::max(link.end(), lastClose)};
rest = QStringView{rest.begin(), std::max(rest.end(), lastClose)};
}
result = Parsed{
.protocol = protocol,
.host = host,
.rest = rest,
.link = link,
};
}
return result;
}
const std::optional<ParsedLink> &LinkParser::result() const
{
return this->result_;
}
} // namespace chatterino
} // namespace chatterino::linkparser

View file

@ -4,43 +4,127 @@
#include <optional>
namespace chatterino {
namespace chatterino::linkparser {
struct ParsedLink {
/// @brief Represents a parsed link
///
/// A parsed link is represented as views over the source string for its
/// different segments. In this simplified model, a link consists of an optional
/// @a protocol, a mandatory @a host and an optional @a rest. These segments are
/// always next to eachother in the input string, however together, they don't
/// span the whole input as it could contain prefixes or suffixes.
///
/// Prefixes and suffixes are almost identical to the ones in GitHub Flavored
/// Markdown (GFM - https://github.github.com/gfm/#autolinks-extension-).
/// The main difference is that '_' isn't a valid suffix.
/// Parentheses are counted inside the @a rest: parsing "(a.com/(foo))" would
/// result in the link "a.com/(foo)".
/// Matching is done case insensitive (e.g. "HTTp://a.com" would be valid).
///
/// A @a protocol can either be empty, "http://", or "https://".
/// A @a host can either be an IPv4 address or a hostname. The hostname must end
/// in a valid top level domain. Otherwise, there are no restrictions on it.
/// The @a rest can start with an optional port followed by either a '/', '?',
/// or '#'.
///
/// @b Example
///
/// ```text
/// (https://wiki.chatterino.com/Help/#overview)
/// ▏▏proto ▕ host ▏ rest ▏▏
/// ▏▏ link ▏▏
/// ▏ source ▏
/// ```
struct Parsed {
/// The parsed protocol of the link. Can be empty.
///
/// ```text
/// https://www.forsen.tv/commands
/// ^------^
/// ▏╌╌╌╌╌╌▕
///
/// www.forsen.tv/commands
/// (empty)
/// ```
QStringView protocol;
/// The parsed host of the link. Can not be empty.
///
/// ```text
/// https://www.forsen.tv/commands
/// ^-----------^
/// ▏╌╌╌╌╌╌╌╌╌╌╌▕
/// ```
QStringView host;
/// The remainder of the link. Can be empty.
///
/// ```text
/// https://www.forsen.tv/commands
/// ^-------^
/// ▏╌╌╌╌╌╌╌▕
///
/// https://www.forsen.tv
/// (empty)
/// ```
QStringView rest;
/// The original unparsed link.
/// The matched link. Can not be empty.
///
/// ```text
/// (https://www.forsen.tv/commands)
/// ▏╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌▕
/// ```
QStringView link;
/// Checks if the parsed link contains a prefix
bool hasPrefix(const QString &source) const noexcept
{
return this->link.begin() != source.begin();
}
/// The prefix before the parsed link inside @a source. May be empty.
///
/// ```text
/// (https://www.forsen.tv/commands)
/// ^
///
/// https://www.forsen.tv/commands
/// ^----------------------------^
QString source;
/// (empty)
/// ```
QStringView prefix(const QString &source) const noexcept
{
return {source.data(), this->link.begin()};
}
/// Checks if the parsed link contains a suffix
bool hasSuffix(const QString &source) const noexcept
{
return this->link.end() != source.end();
}
/// The suffix after the parsed link inside @a source. May be empty.
///
/// ```text
/// (https://www.forsen.tv/commands)
/// ^
///
/// https://www.forsen.tv/commands
/// (empty)
/// ```
QStringView suffix(const QString &source) const noexcept
{
return {
this->link.begin() + this->link.size(),
source.data() + source.length(),
};
}
};
class LinkParser
{
public:
explicit LinkParser(const QString &unparsedString);
/// @brief Parses a link from @a source into its segments
///
/// If no link is contained in @a source, `std::nullopt` will be returned.
/// The returned value is valid as long as @a source exists, as it contains
/// views into @a source.
///
/// For the accepted links, see Parsed.
std::optional<Parsed> parse(const QString &source) noexcept;
const std::optional<ParsedLink> &result() const;
private:
std::optional<ParsedLink> result_{};
};
} // namespace chatterino
} // namespace chatterino::linkparser

View file

@ -1,4 +1,4 @@
#include "Modes.hpp"
#include "common/Modes.hpp"
#include "util/CombinePath.hpp"

View file

@ -127,6 +127,27 @@ public:
this->itemsChanged_();
}
bool removeFirstMatching(std::function<bool(const T &)> matcher,
void *caller = nullptr)
{
assertInGuiThread();
for (int index = 0; index < this->items_.size(); ++index)
{
T item = this->items_[index];
if (matcher(item))
{
this->items_.erase(this->items_.begin() + index);
SignalVectorItemEvent<T> args{item, index, caller};
this->itemRemoved.invoke(args);
this->itemsChanged_();
return true;
}
}
return false;
}
const std::vector<T> &raw() const
{
assertInGuiThread();
@ -155,7 +176,7 @@ public:
decltype(auto) operator[](size_t index)
{
assertInGuiThread();
return this->items[index];
return this->items_[index];
}
auto empty()

View file

@ -1,31 +0,0 @@
#pragma once
namespace chatterino {
class Settings;
class Paths;
class Singleton
{
public:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
Singleton(Singleton &&) = delete;
Singleton &operator=(Singleton &&) = delete;
virtual void initialize(Settings &settings, const Paths &paths)
{
(void)(settings);
(void)(paths);
}
virtual void save()
{
}
};
} // namespace chatterino

View file

@ -1,11 +1,15 @@
#include "common/Version.hpp"
#include "common/Literals.hpp"
#include "common/Modes.hpp"
#include <QFileInfo>
#include <QStringBuilder>
namespace chatterino {
using namespace literals;
Version::Version()
: version_(CHATTERINO_VERSION)
, commitHash_(QStringLiteral(CHATTERINO_GIT_HASH))
@ -79,11 +83,17 @@ QStringList Version::buildTags() const
{
QStringList tags;
tags.append("Qt " QT_VERSION_STR);
const auto *runtimeVersion = qVersion();
if (runtimeVersion != QLatin1String{QT_VERSION_STR})
{
tags.append(u"Qt "_s QT_VERSION_STR u" (running on " % runtimeVersion %
u")");
}
else
{
tags.append(u"Qt "_s QT_VERSION_STR);
}
#ifdef USEWINSDK
tags.append("Windows SDK");
#endif
#ifdef _MSC_FULL_VER
tags.append("MSVC " + QString::number(_MSC_FULL_VER, 10));
#endif

View file

@ -1,7 +1,8 @@
#pragma once
#include <QString>
#include <QtGlobal>
namespace chatterino {
/**
* Valid version formats, in order of latest to oldest
@ -24,21 +25,7 @@
* - 2.4.0-alpha.2
* - 2.4.0-alpha
**/
#define CHATTERINO_VERSION "2.5.1"
#if defined(Q_OS_WIN)
# define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS)
# define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX)
# define CHATTERINO_OS "linux"
#elif defined(Q_OS_FREEBSD)
# define CHATTERINO_OS "freebsd"
#else
# define CHATTERINO_OS "unknown"
#endif
namespace chatterino {
inline const QString CHATTERINO_VERSION = QStringLiteral("2.5.1");
class Version
{

View file

@ -13,6 +13,7 @@ void NetworkManager::init()
assert(!NetworkManager::accessManager);
NetworkManager::workerThread = new QThread;
NetworkManager::workerThread->setObjectName("NetworkWorker");
NetworkManager::workerThread->start();
NetworkManager::accessManager = new QNetworkAccessManager;

View file

@ -59,7 +59,7 @@ void loadUncached(std::shared_ptr<NetworkData> &&data)
void loadCached(std::shared_ptr<NetworkData> &&data)
{
QFile cachedFile(getIApp()->getPaths().cacheDirectory() + "/" +
QFile cachedFile(getApp()->getPaths().cacheDirectory() + "/" +
data->getHash());
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))

View file

@ -4,6 +4,7 @@
#include "common/QLogging.hpp"
#include "common/Version.hpp"
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QtConcurrent>
@ -44,7 +45,7 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
if (caller)
{
// Caller must be in gui thread
assert(caller->thread() == qApp->thread());
assert(caller->thread() == QApplication::instance()->thread());
this->data->caller = const_cast<QObject *>(caller);
this->data->hasCaller = true;

View file

@ -118,7 +118,7 @@ void NetworkTask::logReply()
void NetworkTask::writeToCache(const QByteArray &bytes) const
{
std::ignore = QtConcurrent::run([data = this->data_, bytes] {
QFile cachedFile(getIApp()->getPaths().cacheDirectory() + "/" +
QFile cachedFile(getApp()->getPaths().cacheDirectory() + "/" +
data->getHash());
if (cachedFile.open(QIODevice::WriteOnly))

View file

@ -1,4 +1,4 @@
#include "Account.hpp"
#include "controllers/accounts/Account.hpp"
#include <tuple>

View file

@ -47,7 +47,7 @@ AccountController::AccountController()
});
}
void AccountController::initialize(Settings &settings, const Paths &paths)
void AccountController::load()
{
this->twitch.load();
}

View file

@ -1,7 +1,6 @@
#pragma once
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "providers/twitch/TwitchAccountManager.hpp"
#include <QObject>
@ -14,14 +13,17 @@ class Paths;
class AccountModel;
class AccountController final : public Singleton
class AccountController final
{
public:
AccountController();
AccountModel *createModel(QObject *parent);
void initialize(Settings &settings, const Paths &paths) override;
/**
* Load current user & send off a signal to subscribers about any potential changes
*/
void load();
TwitchAccountManager twitch;

View file

@ -1,4 +1,4 @@
#include "AccountModel.hpp"
#include "controllers/accounts/AccountModel.hpp"
#include "controllers/accounts/Account.hpp"
#include "util/StandardItemHelper.hpp"

View file

@ -1,4 +1,4 @@
#include "Command.hpp"
#include "controllers/commands/Command.hpp"
namespace chatterino {

View file

@ -121,7 +121,7 @@ const std::unordered_map<QString, VariableReplacer> COMMAND_VARS{
(void)(channel); //unused
(void)(message); //unused
auto uid =
getIApp()->getAccounts()->twitch.getCurrent()->getUserId();
getApp()->getAccounts()->twitch.getCurrent()->getUserId();
return uid.isEmpty() ? altText : uid;
},
},
@ -131,7 +131,7 @@ const std::unordered_map<QString, VariableReplacer> COMMAND_VARS{
(void)(channel); //unused
(void)(message); //unused
auto name =
getIApp()->getAccounts()->twitch.getCurrent()->getUserName();
getApp()->getAccounts()->twitch.getCurrent()->getUserName();
return name.isEmpty() ? altText : name;
},
},
@ -263,7 +263,7 @@ const std::unordered_map<QString, VariableReplacer> COMMAND_VARS{
namespace chatterino {
void CommandController::initialize(Settings &, const Paths &paths)
CommandController::CommandController(const Paths &paths)
{
// Update commands map when the vector of commands has been updated
auto addFirstMatchToMap = [this](auto args) {
@ -459,6 +459,8 @@ void CommandController::initialize(Settings &, const Paths &paths)
this->registerCommand("/debug-force-image-unload",
&commands::forceImageUnload);
this->registerCommand("/debug-test", &commands::debugTest);
this->registerCommand("/shield", &commands::shieldModeOn);
this->registerCommand("/shieldoff", &commands::shieldModeOff);
@ -485,7 +487,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
ChannelPtr channel, bool dryRun)
{
QString text =
getIApp()->getEmotes()->getEmojis()->replaceShortCodes(textNoEmoji);
getApp()->getEmotes()->getEmojis()->replaceShortCodes(textNoEmoji);
QStringList words = text.split(' ', Qt::SkipEmptyParts);
if (words.length() == 0)
@ -500,7 +502,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
const auto it = this->userCommands_.find(commandName);
if (it != this->userCommands_.end())
{
text = getIApp()->getEmotes()->getEmojis()->replaceShortCodes(
text = getApp()->getEmotes()->getEmojis()->replaceShortCodes(
this->execCustomCommand(words, it.value(), dryRun, channel));
words = text.split(' ', Qt::SkipEmptyParts);
@ -554,8 +556,7 @@ QString CommandController::execCommand(const QString &textNoEmoji,
if (!dryRun && channel->getType() == Channel::Type::TwitchWhispers)
{
channel->addMessage(
makeSystemMessage("Use /w <username> <message> to whisper"));
channel->addSystemMessage("Use /w <username> <message> to whisper");
return "";
}
@ -571,7 +572,7 @@ bool CommandController::registerPluginCommand(const QString &commandName)
}
this->commands_[commandName] = [commandName](const CommandContext &ctx) {
return getIApp()->getPlugins()->tryExecPluginCommand(commandName, ctx);
return getApp()->getPlugins()->tryExecPluginCommand(commandName, ctx);
};
this->pluginCommands_.append(commandName);
return true;

View file

@ -1,7 +1,6 @@
#pragma once
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "util/QStringHash.hpp"
#include <pajlada/settings.hpp>
@ -24,7 +23,7 @@ struct Command;
class CommandModel;
struct CommandContext;
class CommandController final : public Singleton
class CommandController final
{
public:
SignalVector<Command> items;
@ -33,8 +32,8 @@ public:
bool dryRun);
QStringList getDefaultChatterinoCommandList();
void initialize(Settings &, const Paths &paths) override;
void save() override;
CommandController(const Paths &paths);
void save();
CommandModel *createModel(QObject *parent);

View file

@ -5,7 +5,6 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "controllers/userdata/UserDataController.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -39,10 +38,10 @@ QString follow(const CommandContext &ctx)
{
return "";
}
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Twitch has removed the ability to follow users through "
"third-party applications. For more information, see "
"https://github.com/Chatterino/chatterino2/issues/3076"));
"https://github.com/Chatterino/chatterino2/issues/3076");
return "";
}
@ -52,10 +51,10 @@ QString unfollow(const CommandContext &ctx)
{
return "";
}
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Twitch has removed the ability to unfollow users through "
"third-party applications. For more information, see "
"https://github.com/Chatterino/chatterino2/issues/3076"));
"https://github.com/Chatterino/chatterino2/issues/3076");
return "";
}
@ -68,8 +67,8 @@ QString uptime(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /uptime command only works in Twitch Channels."));
ctx.channel->addSystemMessage(
"The /uptime command only works in Twitch Channels.");
return "";
}
@ -78,7 +77,7 @@ QString uptime(const CommandContext &ctx)
QString messageText =
streamStatus->live ? streamStatus->uptime : "Channel is not live.";
ctx.channel->addMessage(makeSystemMessage(messageText));
ctx.channel->addSystemMessage(messageText);
return "";
}
@ -92,8 +91,7 @@ QString user(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /user <user> [channel]"));
ctx.channel->addSystemMessage("Usage: /user <user> [channel]");
return "";
}
QString userName = ctx.words[1];
@ -129,11 +127,11 @@ QString requests(const CommandContext &ctx)
}
else
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: /requests [channel]. You can also use the command "
"without arguments in any Twitch channel to open its "
"channel points requests queue. Only the broadcaster and "
"moderators have permission to view the queue."));
"moderators have permission to view the queue.");
return "";
}
}
@ -163,11 +161,11 @@ QString lowtrust(const CommandContext &ctx)
}
else
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: /lowtrust [channel]. You can also use the command "
"without arguments in any Twitch channel to open its "
"suspicious user activity feed. Only the broadcaster and "
"moderators have permission to view this feed."));
"moderators have permission to view this feed.");
return "";
}
}
@ -190,15 +188,15 @@ QString clip(const CommandContext &ctx)
if (const auto type = ctx.channel->getType();
type != Channel::Type::Twitch && type != Channel::Type::TwitchWatching)
{
ctx.channel->addMessage(makeSystemMessage(
"The /clip command only works in Twitch Channels."));
ctx.channel->addSystemMessage(
"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."));
ctx.channel->addSystemMessage(
"The /clip command only works in Twitch Channels.");
return "";
}
@ -216,25 +214,25 @@ QString marker(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /marker command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /marker command only works in Twitch channels.");
return "";
}
// Avoid Helix calls without Client ID and/or OAuth Token
if (getIApp()->getAccounts()->twitch.getCurrent()->isAnon())
if (getApp()->getAccounts()->twitch.getCurrent()->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
"You need to be logged in to create stream markers!"));
ctx.channel->addSystemMessage(
"You need to be logged in to create stream markers!");
return "";
}
// Exact same message as in webchat
if (!ctx.twitchChannel->isLive())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"You can only add stream markers during live streams. Try "
"again when the channel is live streaming."));
"again when the channel is live streaming.");
return "";
}
@ -247,13 +245,13 @@ QString marker(const CommandContext &ctx)
ctx.twitchChannel->roomId(), arguments.join(" ").left(140),
[channel{ctx.channel},
arguments](const HelixStreamMarker &streamMarker) {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Successfully added a stream marker at %1%2")
.arg(formatTime(streamMarker.positionSeconds))
.arg(streamMarker.description.isEmpty()
? ""
: QString(": \"%1\"")
.arg(streamMarker.description))));
.arg(streamMarker.description)));
},
[channel{ctx.channel}](auto error) {
QString errorMessage("Failed to create stream marker - ");
@ -279,7 +277,7 @@ QString marker(const CommandContext &ctx)
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";
@ -303,10 +301,10 @@ QString streamlink(const CommandContext &ctx)
}
else
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"/streamlink [channel]. Open specified Twitch channel in "
"streamlink. If no channel argument is specified, open the "
"current Twitch channel instead."));
"current Twitch channel instead.");
return "";
}
}
@ -335,10 +333,10 @@ QString popout(const CommandContext &ctx)
}
else
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: /popout <channel>. You can also use the command "
"without arguments in any Twitch channel to open its "
"popout chat."));
"popout chat.");
return "";
}
}
@ -369,7 +367,7 @@ QString popup(const CommandContext &ctx)
if (target.isEmpty())
{
auto *currentPage =
dynamic_cast<SplitContainer *>(getIApp()
dynamic_cast<SplitContainer *>(getApp()
->getWindows()
->getMainWindow()
.getNotebook()
@ -385,14 +383,13 @@ QString popup(const CommandContext &ctx)
}
}
ctx.channel->addMessage(makeSystemMessage(usageMessage));
ctx.channel->addSystemMessage(usageMessage);
return "";
}
// Open channel passed as argument in a popup
auto targetChannel =
getIApp()->getTwitchAbstract()->getOrAddChannel(target);
getIApp()->getWindows()->openInPopup(targetChannel);
auto targetChannel = getApp()->getTwitch()->getOrAddChannel(target);
getApp()->getWindows()->openInPopup(targetChannel);
return "";
}
@ -401,11 +398,11 @@ QString clearmessages(const CommandContext &ctx)
{
(void)ctx;
auto *currentPage = dynamic_cast<SplitContainer *>(getIApp()
auto *currentPage = getApp()
->getWindows()
->getMainWindow()
.getNotebook()
.getSelectedPage());
->getLastSelectedWindow()
->getNotebook()
.getSelectedPage();
if (auto *split = currentPage->getSelectedSplit())
{
@ -469,8 +466,8 @@ QString openURL(const CommandContext &ctx)
const auto &positionalArguments = parser.positionalArguments();
if (positionalArguments.isEmpty())
{
ctx.channel->addMessage(makeSystemMessage(
"Usage: /openurl <URL> [--incognito/--no-incognito]"));
ctx.channel->addSystemMessage(
"Usage: /openurl <URL> [--incognito/--no-incognito]");
return "";
}
auto urlString = parser.positionalArguments().join(' ');
@ -478,7 +475,7 @@ QString openURL(const CommandContext &ctx)
QUrl url = QUrl::fromUserInput(urlString);
if (!url.isValid())
{
ctx.channel->addMessage(makeSystemMessage("Invalid URL specified."));
ctx.channel->addSystemMessage("Invalid URL specified.");
return "";
}
@ -488,9 +485,9 @@ QString openURL(const CommandContext &ctx)
if (forcePrivateMode && forceNonPrivateMode)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Error: /openurl may only be called with --incognito or "
"--no-incognito, not both at the same time."));
"--no-incognito, not both at the same time.");
return "";
}
@ -518,7 +515,7 @@ QString openURL(const CommandContext &ctx)
if (!res)
{
ctx.channel->addMessage(makeSystemMessage("Could not open URL."));
ctx.channel->addSystemMessage("Could not open URL.");
}
return "";
@ -533,8 +530,7 @@ QString sendRawMessage(const CommandContext &ctx)
if (ctx.channel->isTwitchChannel())
{
getIApp()->getTwitchAbstract()->sendRawMessage(
ctx.words.mid(1).join(" "));
getApp()->getTwitch()->sendRawMessage(ctx.words.mid(1).join(" "));
}
else
{
@ -553,21 +549,21 @@ QString injectFakeMessage(const CommandContext &ctx)
if (!ctx.channel->isTwitchChannel())
{
ctx.channel->addMessage(makeSystemMessage(
"The /fakemsg command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /fakemsg command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: /fakemsg (raw irc text) - injects raw irc text as "
"if it was a message received from TMI"));
"if it was a message received from TMI");
return "";
}
auto ircText = ctx.words.mid(1).join(" ");
getIApp()->getTwitchAbstract()->addFakeMessage(ircText);
getApp()->getTwitch()->addFakeMessage(ircText);
return "";
}
@ -583,13 +579,13 @@ QString injectStreamUpdateNoStream(const CommandContext &ctx)
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(
makeSystemMessage("The /debug-update-to-no-stream command only "
"works in Twitch channels"));
ctx.channel->addSystemMessage(
"The /debug-update-to-no-stream command only "
"works in Twitch channels");
return "";
}
ctx.twitchChannel->updateStreamStatus(std::nullopt);
ctx.twitchChannel->updateStreamStatus(std::nullopt, false);
return "";
}
@ -602,9 +598,8 @@ QString copyToClipboard(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /copy <text> - copies provided "
"text to clipboard."));
ctx.channel->addSystemMessage("Usage: /copy <text> - copies provided "
"text to clipboard.");
return "";
}
@ -621,15 +616,15 @@ QString unstableSetUserClientSideColor(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(
makeSystemMessage("The /unstable-set-user-color command only "
"works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /unstable-set-user-color command only "
"works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
QString("Usage: %1 <TwitchUserID> [color]").arg(ctx.words.at(0))));
ctx.channel->addSystemMessage(
QString("Usage: %1 <TwitchUserID> [color]").arg(ctx.words.at(0)));
return "";
}
@ -637,7 +632,7 @@ QString unstableSetUserClientSideColor(const CommandContext &ctx)
auto color = ctx.words.value(2);
getIApp()->getUserData()->setUserColor(userID, color);
getApp()->getUserData()->setUserColor(userID, color);
return "";
}
@ -653,9 +648,8 @@ QString openUsercard(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
channel->addMessage(
makeSystemMessage("Usage: /usercard <username> [channel] or "
"/usercard id:<id> [channel]"));
channel->addSystemMessage("Usage: /usercard <username> [channel] or "
"/usercard id:<id> [channel]");
return "";
}
@ -668,13 +662,13 @@ QString openUsercard(const CommandContext &ctx)
stripChannelName(channelName);
ChannelPtr channelTemp =
getIApp()->getTwitchAbstract()->getChannelOrEmpty(channelName);
getApp()->getTwitch()->getChannelOrEmpty(channelName);
if (channelTemp->isEmpty())
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
"A usercard can only be displayed for a channel that is "
"currently opened in Chatterino."));
"currently opened in Chatterino.");
return "";
}
@ -683,7 +677,7 @@ QString openUsercard(const CommandContext &ctx)
// try to link to current split if possible
Split *currentSplit = nullptr;
auto *currentPage = dynamic_cast<SplitContainer *>(getIApp()
auto *currentPage = dynamic_cast<SplitContainer *>(getApp()
->getWindows()
->getMainWindow()
.getNotebook()
@ -699,7 +693,7 @@ QString openUsercard(const CommandContext &ctx)
{
// not possible to use current split, try searching for one
const auto &notebook =
getIApp()->getWindows()->getMainWindow().getNotebook();
getApp()->getWindows()->getMainWindow().getNotebook();
auto count = notebook.getPageCount();
for (int i = 0; i < count; i++)
{

View file

@ -22,12 +22,12 @@ QString setLoggingRules(const CommandContext &ctx)
{
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: /c2-set-logging-rules <rules...>. To enable debug logging "
"for all categories from chatterino, use "
"'chatterino.*.debug=true'. For the format on the rules, see "
"https://doc.qt.io/qt-6/"
"qloggingcategory.html#configuring-categories"));
"qloggingcategory.html#configuring-categories");
return {};
}
@ -47,7 +47,7 @@ QString setLoggingRules(const CommandContext &ctx)
"https://doc.qt.io/qt-6/qloggingcategory.html#setFilterRules");
}
ctx.channel->addMessage(makeSystemMessage(message));
ctx.channel->addSystemMessage(message);
return {};
}
@ -56,15 +56,13 @@ QString toggleThemeReload(const CommandContext &ctx)
if (getTheme()->isAutoReloading())
{
getTheme()->setAutoReload(false);
ctx.channel->addMessage(
makeSystemMessage(u"Disabled theme auto reloading."_s));
ctx.channel->addSystemMessage(u"Disabled theme auto reloading."_s);
return {};
}
getTheme()->setAutoReload(true);
ctx.channel->addMessage(
makeSystemMessage(u"Auto reloading theme every %1 ms."_s.arg(
Theme::AUTO_RELOAD_INTERVAL_MS)));
ctx.channel->addSystemMessage(u"Auto reloading theme every %1 ms."_s.arg(
Theme::AUTO_RELOAD_INTERVAL_MS));
return {};
}
@ -92,7 +90,7 @@ QString listEnvironmentVariables(const CommandContext &ctx)
builder.emplace<TimestampElement>(QTime::currentTime());
builder.emplace<TextElement>(str, MessageElementFlag::Text,
MessageColor::System);
channel->addMessage(builder.release());
channel->addMessage(builder.release(), MessageContext::Original);
}
return "";
}
@ -107,7 +105,7 @@ QString listArgs(const CommandContext &ctx)
QString msg = QApplication::instance()->arguments().join(' ');
channel->addMessage(makeSystemMessage(msg));
channel->addSystemMessage(msg);
return "";
}
@ -134,4 +132,16 @@ QString forceImageUnload(const CommandContext &ctx)
return "";
}
QString debugTest(const CommandContext &ctx)
{
if (!ctx.channel)
{
return "";
}
ctx.channel->addSystemMessage("debug-test called");
return "";
}
} // namespace chatterino::commands

View file

@ -22,4 +22,6 @@ QString forceImageGarbageCollection(const CommandContext &ctx);
QString forceImageUnload(const CommandContext &ctx);
QString debugTest(const CommandContext &ctx);
} // namespace chatterino::commands

View file

@ -1,14 +1,11 @@
#include "controllers/commands/builtin/twitch/AddModerator.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/Twitch.hpp"
namespace chatterino::commands {
@ -22,23 +19,22 @@ QString addModerator(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /mod command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /mod command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/mod <username>\" - Grant moderator status to a "
"user. Use \"/mods\" to list the moderators of this channel."));
"user. Use \"/mods\" to list the moderators of this channel.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to mod someone!"));
ctx.channel->addSystemMessage("You must be logged in to mod someone!");
return "";
}
@ -52,10 +48,10 @@ QString addModerator(const CommandContext &ctx)
getHelix()->addChannelModerator(
twitchChannel->roomId(), targetUser.id,
[channel, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You have added %1 as a moderator of this "
"channel.")
.arg(targetUser.displayName)));
.arg(targetUser.displayName));
},
[channel, targetUser](auto error, auto message) {
QString errorMessage =
@ -116,13 +112,13 @@ QString addModerator(const CommandContext &ctx)
}
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";

View file

@ -1,14 +1,11 @@
#include "controllers/commands/builtin/twitch/AddVIP.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/Twitch.hpp"
namespace chatterino::commands {
@ -22,23 +19,22 @@ QString addVIP(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /vip command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /vip command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/vip <username>\" - Grant VIP status to a user. Use "
"\"/vips\" to list the VIPs of this channel."));
"\"/vips\" to list the VIPs of this channel.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to VIP someone!"));
ctx.channel->addSystemMessage("You must be logged in to VIP someone!");
return "";
}
@ -52,9 +48,9 @@ QString addVIP(const CommandContext &ctx)
getHelix()->addChannelVIP(
twitchChannel->roomId(), targetUser.id,
[channel, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You have added %1 as a VIP of this channel.")
.arg(targetUser.displayName)));
.arg(targetUser.displayName));
},
[channel, targetUser](auto error, auto message) {
QString errorMessage = QString("Failed to add VIP - ");
@ -97,13 +93,13 @@ QString addVIP(const CommandContext &ctx)
}
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";

View file

@ -1,10 +1,8 @@
#include "controllers/commands/builtin/twitch/Announce.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -22,8 +20,8 @@ QString sendAnnouncementColor(const CommandContext &ctx,
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"This command can only be used in Twitch channels."));
ctx.channel->addSystemMessage(
"This command can only be used in Twitch channels.");
return "";
}
@ -48,16 +46,16 @@ QString sendAnnouncementColor(const CommandContext &ctx,
"message with a %1 highlight.")
.arg(colorStr);
}
ctx.channel->addMessage(makeSystemMessage(usageMsg));
ctx.channel->addSystemMessage(usageMsg);
return "";
}
auto user = getIApp()->getAccounts()->twitch.getCurrent();
auto user = getApp()->getAccounts()->twitch.getCurrent();
if (user->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QString("You must be logged in to use the /announce%1 command.")
.arg(colorStr)));
.arg(colorStr));
return "";
}
@ -93,7 +91,7 @@ QString sendAnnouncementColor(const CommandContext &ctx,
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";
}

View file

@ -5,7 +5,6 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "controllers/commands/common/ChannelAction.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -93,7 +92,7 @@ void banUserByID(const ChannelPtr &channel, const QString &channelID,
[channel, displayName](auto error, auto message) {
auto errorMessage =
formatBanTimeoutError("ban", error, message, displayName);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
}
@ -110,7 +109,7 @@ void timeoutUserByID(const ChannelPtr &channel, const QString &channelID,
[channel, displayName](auto error, auto message) {
auto errorMessage =
formatBanTimeoutError("timeout", error, message, displayName);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
}
@ -129,7 +128,7 @@ QString sendBan(const CommandContext &ctx)
{
if (ctx.channel != nullptr)
{
ctx.channel->addMessage(makeSystemMessage(actions.error()));
ctx.channel->addSystemMessage(actions.error());
}
else
{
@ -142,11 +141,10 @@ QString sendBan(const CommandContext &ctx)
assert(!actions.value().empty());
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to ban someone!"));
ctx.channel->addSystemMessage("You must be logged in to ban someone!");
return "";
}
@ -192,16 +190,16 @@ QString sendBan(const CommandContext &ctx)
userLoginsToFetch](const auto &users) mutable {
if (!actionChannel.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to ban, bad channel name: %1")
.arg(actionChannel.login)));
.arg(actionChannel.login));
return;
}
if (!actionTarget.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to ban, bad target name: %1")
.arg(actionTarget.login)));
.arg(actionTarget.login));
return;
}
@ -210,9 +208,9 @@ QString sendBan(const CommandContext &ctx)
reason, actionTarget.displayName);
},
[channel{ctx.channel}, userLoginsToFetch] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to ban, bad username(s): %1")
.arg(userLoginsToFetch.join(", "))));
.arg(userLoginsToFetch.join(", ")));
});
}
else
@ -239,8 +237,8 @@ QString sendBanById(const CommandContext &ctx)
}
if (twitchChannel == nullptr)
{
channel->addMessage(makeSystemMessage(
QString("The /banid command only works in Twitch channels.")));
channel->addSystemMessage(
"The /banid command only works in Twitch channels.");
return "";
}
@ -250,15 +248,14 @@ QString sendBanById(const CommandContext &ctx)
"shown to the target user and other moderators.";
if (words.size() < 2)
{
channel->addMessage(makeSystemMessage(usageStr));
channel->addSystemMessage(usageStr);
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
channel->addMessage(
makeSystemMessage("You must be logged in to ban someone!"));
channel->addSystemMessage("You must be logged in to ban someone!");
return "";
}
@ -282,7 +279,7 @@ QString sendTimeout(const CommandContext &ctx)
{
if (ctx.channel != nullptr)
{
ctx.channel->addMessage(makeSystemMessage(actions.error()));
ctx.channel->addSystemMessage(actions.error());
}
else
{
@ -295,11 +292,11 @@ QString sendTimeout(const CommandContext &ctx)
assert(!actions.value().empty());
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to timeout someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to timeout someone!");
return "";
}
@ -346,16 +343,16 @@ QString sendTimeout(const CommandContext &ctx)
userLoginsToFetch](const auto &users) mutable {
if (!actionChannel.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad channel name: %1")
.arg(actionChannel.login)));
.arg(actionChannel.login));
return;
}
if (!actionTarget.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad target name: %1")
.arg(actionTarget.login)));
.arg(actionTarget.login));
return;
}
@ -364,9 +361,9 @@ QString sendTimeout(const CommandContext &ctx)
duration, reason, actionTarget.displayName);
},
[channel{ctx.channel}, userLoginsToFetch] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad username(s): %1")
.arg(userLoginsToFetch.join(", "))));
.arg(userLoginsToFetch.join(", ")));
});
}
else

View file

@ -4,7 +4,6 @@
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "util/Twitch.hpp"
@ -26,23 +25,23 @@ QString blockUser(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /block command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /block command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage("Usage: /block <user>"));
ctx.channel->addSystemMessage("Usage: /block <user>");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to block someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to block someone!");
return "";
}
@ -53,25 +52,24 @@ QString blockUser(const CommandContext &ctx)
target,
[currentUser, channel{ctx.channel},
target](const HelixUser &targetUser) {
getIApp()->getAccounts()->twitch.getCurrent()->blockUser(
getApp()->getAccounts()->twitch.getCurrent()->blockUser(
targetUser.id, nullptr,
[channel, target, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You successfully blocked user %1")
.arg(target)));
.arg(target));
},
[channel, target] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("User %1 couldn't be blocked, an unknown "
"error occurred!")
.arg(target)));
.arg(target));
});
},
[channel{ctx.channel}, target] {
channel->addMessage(
makeSystemMessage(QString("User %1 couldn't be blocked, no "
channel->addSystemMessage(QString("User %1 couldn't be blocked, no "
"user with that name found!")
.arg(target)));
.arg(target));
});
return "";
@ -84,9 +82,9 @@ QString ignoreUser(const CommandContext &ctx)
return "";
}
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Ignore command has been renamed to /block, please use it from "
"now on as /ignore is going to be removed soon."));
"now on as /ignore is going to be removed soon.");
return blockUser(ctx);
}
@ -100,23 +98,23 @@ QString unblockUser(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /unblock command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /unblock command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage("Usage: /unblock <user>"));
ctx.channel->addSystemMessage("Usage: /unblock <user>");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to unblock someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to unblock someone!");
return "";
}
@ -126,25 +124,24 @@ QString unblockUser(const CommandContext &ctx)
getHelix()->getUserByName(
target,
[currentUser, channel{ctx.channel}, target](const auto &targetUser) {
getIApp()->getAccounts()->twitch.getCurrent()->unblockUser(
getApp()->getAccounts()->twitch.getCurrent()->unblockUser(
targetUser.id, nullptr,
[channel, target, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You successfully unblocked user %1")
.arg(target)));
.arg(target));
},
[channel, target] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("User %1 couldn't be unblocked, an unknown "
"error occurred!")
.arg(target)));
.arg(target));
});
},
[channel{ctx.channel}, target] {
channel->addMessage(
makeSystemMessage(QString("User %1 couldn't be unblocked, "
channel->addSystemMessage(QString("User %1 couldn't be unblocked, "
"no user with that name found!")
.arg(target)));
.arg(target));
});
return "";
@ -157,9 +154,9 @@ QString unignoreUser(const CommandContext &ctx)
return "";
}
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Unignore command has been renamed to /unblock, please use it "
"from now on as /unignore is going to be removed soon."));
"from now on as /unignore is going to be removed soon.");
return unblockUser(ctx);
}

View file

@ -3,7 +3,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -87,8 +86,8 @@ auto successCallback = [](auto result) {};
auto failureCallback = [](ChannelPtr channel, int durationUnitMultiplier = 1) {
return [channel, durationUnitMultiplier](const auto &error,
const QString &message) {
channel->addMessage(makeSystemMessage(
formatError(error, message, durationUnitMultiplier)));
channel->addSystemMessage(
formatError(error, message, durationUnitMultiplier));
};
};
@ -101,24 +100,24 @@ namespace chatterino::commands {
QString emoteOnly(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /emoteonly command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /emoteonly command only works in Twitch channels.");
return "";
}
if (ctx.twitchChannel->accessRoomModes()->emoteOnly)
{
ctx.channel->addMessage(
makeSystemMessage("This room is already in emote-only mode."));
ctx.channel->addSystemMessage(
"This room is already in emote-only mode.");
return "";
}
@ -131,23 +130,22 @@ QString emoteOnly(const CommandContext &ctx)
QString emoteOnlyOff(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /emoteonlyoff command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /emoteonlyoff command only works in Twitch channels.");
return "";
}
if (!ctx.twitchChannel->accessRoomModes()->emoteOnly)
{
ctx.channel->addMessage(
makeSystemMessage("This room is not in emote-only mode."));
ctx.channel->addSystemMessage("This room is not in emote-only mode.");
return "";
}
@ -160,24 +158,24 @@ QString emoteOnlyOff(const CommandContext &ctx)
QString subscribers(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /subscribers command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /subscribers command only works in Twitch channels.");
return "";
}
if (ctx.twitchChannel->accessRoomModes()->submode)
{
ctx.channel->addMessage(makeSystemMessage(
"This room is already in subscribers-only mode."));
ctx.channel->addSystemMessage(
"This room is already in subscribers-only mode.");
return "";
}
@ -190,24 +188,24 @@ QString subscribers(const CommandContext &ctx)
QString subscribersOff(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /subscribersoff command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /subscribersoff command only works in Twitch channels.");
return "";
}
if (!ctx.twitchChannel->accessRoomModes()->submode)
{
ctx.channel->addMessage(
makeSystemMessage("This room is not in subscribers-only mode."));
ctx.channel->addSystemMessage(
"This room is not in subscribers-only mode.");
return "";
}
@ -220,17 +218,17 @@ QString subscribersOff(const CommandContext &ctx)
QString slow(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /slow command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /slow command only works in Twitch channels.");
return "";
}
@ -241,20 +239,20 @@ QString slow(const CommandContext &ctx)
duration = ctx.words.at(1).toInt(&ok);
if (!ok || duration <= 0)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/slow [duration]\" - Enables slow mode (limit how "
"often users may send messages). Duration (optional, "
"default=30) must be a positive number of seconds. Use "
"\"slowoff\" to disable."));
"\"slowoff\" to disable.");
return "";
}
}
if (ctx.twitchChannel->accessRoomModes()->slowMode == duration)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QString("This room is already in %1-second slow mode.")
.arg(duration)));
.arg(duration));
return "";
}
@ -267,24 +265,23 @@ QString slow(const CommandContext &ctx)
QString slowOff(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /slowoff command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /slowoff command only works in Twitch channels.");
return "";
}
if (ctx.twitchChannel->accessRoomModes()->slowMode <= 0)
{
ctx.channel->addMessage(
makeSystemMessage("This room is not in slow mode."));
ctx.channel->addSystemMessage("This room is not in slow mode.");
return "";
}
@ -297,17 +294,17 @@ QString slowOff(const CommandContext &ctx)
QString followers(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /followers command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /followers command only works in Twitch channels.");
return "";
}
@ -319,20 +316,20 @@ QString followers(const CommandContext &ctx)
// -1 / 60 == 0 => use parsed
if (parsed < 0)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/followers [duration]\" - Enables followers-only "
"mode (only users who have followed for 'duration' may chat). "
"Examples: \"30m\", \"1 week\", \"5 days 12 hours\". Must be "
"less than 3 months."));
"less than 3 months.");
return "";
}
}
if (ctx.twitchChannel->accessRoomModes()->followerOnly == duration)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QString("This room is already in %1 followers-only mode.")
.arg(formatTime(duration * 60))));
.arg(formatTime(duration * 60)));
return "";
}
@ -345,24 +342,24 @@ QString followers(const CommandContext &ctx)
QString followersOff(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /followersoff command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /followersoff command only works in Twitch channels.");
return "";
}
if (ctx.twitchChannel->accessRoomModes()->followerOnly < 0)
{
ctx.channel->addMessage(
makeSystemMessage("This room is not in followers-only mode. "));
ctx.channel->addSystemMessage(
"This room is not in followers-only mode. ");
return "";
}
@ -375,24 +372,24 @@ QString followersOff(const CommandContext &ctx)
QString uniqueChat(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /uniquechat command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /uniquechat command only works in Twitch channels.");
return "";
}
if (ctx.twitchChannel->accessRoomModes()->r9k)
{
ctx.channel->addMessage(
makeSystemMessage("This room is already in unique-chat mode."));
ctx.channel->addSystemMessage(
"This room is already in unique-chat mode.");
return "";
}
@ -405,24 +402,23 @@ QString uniqueChat(const CommandContext &ctx)
QString uniqueChatOff(const CommandContext &ctx)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(P_NOT_LOGGED_IN));
ctx.channel->addSystemMessage(P_NOT_LOGGED_IN);
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /uniquechatoff command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /uniquechatoff command only works in Twitch channels.");
return "";
}
if (!ctx.twitchChannel->accessRoomModes()->r9k)
{
ctx.channel->addMessage(
makeSystemMessage("This room is not in unique-chat mode."));
ctx.channel->addSystemMessage("This room is not in unique-chat mode.");
return "";
}

View file

@ -11,7 +11,6 @@
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "singletons/Theme.hpp"
#include <QApplication>
@ -69,23 +68,22 @@ QString chatters(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /chatters command only works in Twitch Channels."));
ctx.channel->addSystemMessage(
"The /chatters command only works in Twitch Channels.");
return "";
}
// Refresh chatter list via helix api for mods
getHelix()->getChatters(
ctx.twitchChannel->roomId(),
getIApp()->getAccounts()->twitch.getCurrent()->getUserId(), 1,
getApp()->getAccounts()->twitch.getCurrent()->getUserId(), 1,
[channel{ctx.channel}](auto result) {
channel->addMessage(
makeSystemMessage(QString("Chatter count: %1.")
.arg(localizeNumbers(result.total))));
channel->addSystemMessage(QString("Chatter count: %1.")
.arg(localizeNumbers(result.total)));
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatChattersError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";
@ -100,14 +98,14 @@ QString testChatters(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /test-chatters command only works in Twitch Channels."));
ctx.channel->addSystemMessage(
"The /test-chatters command only works in Twitch Channels.");
return "";
}
getHelix()->getChatters(
ctx.twitchChannel->roomId(),
getIApp()->getAccounts()->twitch.getCurrent()->getUserId(), 5000,
getApp()->getAccounts()->twitch.getCurrent()->getUserId(), 5000,
[channel{ctx.channel}, twitchChannel{ctx.twitchChannel}](auto result) {
QStringList entries;
for (const auto &username : result.chatters)
@ -126,15 +124,13 @@ QString testChatters(const CommandContext &ctx)
prefix += QString("(%1):").arg(result.total);
}
MessageBuilder builder;
TwitchMessageBuilder::listOfUsersSystemMessage(
prefix, entries, twitchChannel, &builder);
channel->addMessage(builder.release());
channel->addMessage(MessageBuilder::makeListOfUsersMessage(
prefix, entries, twitchChannel),
MessageContext::Original);
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatChattersError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -21,14 +21,14 @@ QString deleteMessages(TwitchChannel *twitchChannel, const QString &messageID)
{
const auto *commandName = messageID.isEmpty() ? "/clear" : "/delete";
auto user = getIApp()->getAccounts()->twitch.getCurrent();
auto user = getApp()->getAccounts()->twitch.getCurrent();
// Avoid Helix calls without Client ID and/or OAuth Token
if (user->isAnon())
{
twitchChannel->addMessage(makeSystemMessage(
twitchChannel->addSystemMessage(
QString("You must be logged in to use the %1 command.")
.arg(commandName)));
.arg(commandName));
return "";
}
@ -82,7 +82,7 @@ QString deleteMessages(TwitchChannel *twitchChannel, const QString &messageID)
break;
}
twitchChannel->addMessage(makeSystemMessage(errorMessage));
twitchChannel->addSystemMessage(errorMessage);
});
return "";
@ -101,8 +101,8 @@ QString deleteAllMessages(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /clear command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /clear command only works in Twitch channels.");
return "";
}
@ -120,16 +120,15 @@ QString deleteOneMessage(const CommandContext &ctx)
// We use this to ensure the user gets better error messages for missing or malformed arguments
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /delete command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /delete command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /delete <msg-id> - Deletes the "
"specified message."));
ctx.channel->addSystemMessage("Usage: /delete <msg-id> - Deletes the "
"specified message.");
return "";
}
@ -138,8 +137,8 @@ QString deleteOneMessage(const CommandContext &ctx)
if (uuid.isNull())
{
// The message id must be a valid UUID
ctx.channel->addMessage(makeSystemMessage(
QString("Invalid msg-id: \"%1\"").arg(messageID)));
ctx.channel->addSystemMessage(
QString("Invalid msg-id: \"%1\"").arg(messageID));
return "";
}
@ -149,9 +148,9 @@ QString deleteOneMessage(const CommandContext &ctx)
if (msg->loginName == ctx.channel->getName() &&
!ctx.channel->isBroadcaster())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"You cannot delete the broadcaster's messages unless "
"you are the broadcaster."));
"you are the broadcaster.");
return "";
}
}

View file

@ -5,7 +5,6 @@
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
namespace {
@ -60,8 +59,8 @@ QString getModerators(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /mods command only works in Twitch Channels."));
ctx.channel->addSystemMessage(
"The /mods command only works in Twitch Channels.");
return "";
}
@ -70,22 +69,21 @@ QString getModerators(const CommandContext &ctx)
[channel{ctx.channel}, twitchChannel{ctx.twitchChannel}](auto result) {
if (result.empty())
{
channel->addMessage(makeSystemMessage(
"This channel does not have any moderators."));
channel->addSystemMessage(
"This channel does not have any moderators.");
return;
}
// TODO: sort results?
MessageBuilder builder;
TwitchMessageBuilder::listOfUsersSystemMessage(
"The moderators of this channel are", result, twitchChannel,
&builder);
channel->addMessage(builder.release());
channel->addMessage(MessageBuilder::makeListOfUsersMessage(
"The moderators of this channel are",
result, twitchChannel),
MessageContext::Original);
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatModsError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -7,8 +7,6 @@
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/Twitch.hpp"
namespace {
@ -77,19 +75,19 @@ QString getVIPs(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /vips command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /vips command only works in Twitch channels.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Due to Twitch restrictions, " //
"this command can only be used by the broadcaster. "
"To see the list of VIPs you must use the "
"Twitch website."));
"Twitch website.");
return "";
}
@ -99,23 +97,22 @@ QString getVIPs(const CommandContext &ctx)
const std::vector<HelixVip> &vipList) {
if (vipList.empty())
{
channel->addMessage(
makeSystemMessage("This channel does not have any VIPs."));
channel->addSystemMessage(
"This channel does not have any VIPs.");
return;
}
auto messagePrefix = QString("The VIPs of this channel are");
// TODO: sort results?
MessageBuilder builder;
TwitchMessageBuilder::listOfUsersSystemMessage(
messagePrefix, vipList, twitchChannel, &builder);
channel->addMessage(builder.release());
channel->addMessage(MessageBuilder::makeListOfUsersMessage(
messagePrefix, vipList, twitchChannel),
MessageContext::Original);
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatGetVIPsError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -3,7 +3,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -124,24 +123,23 @@ QString startRaid(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /raid command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /raid command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: \"/raid <username>\" - Raid a user. "
"Only the broadcaster can start a raid."));
ctx.channel->addSystemMessage(
"Usage: \"/raid <username>\" - Raid a user. "
"Only the broadcaster can start a raid.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to start a raid!"));
ctx.channel->addSystemMessage("You must be logged in to start a raid!");
return "";
}
@ -154,20 +152,18 @@ QString startRaid(const CommandContext &ctx)
channel{ctx.channel}](const HelixUser &targetUser) {
getHelix()->startRaid(
twitchChannel->roomId(), targetUser.id,
[channel, targetUser] {
channel->addMessage(
makeSystemMessage(QString("You started to raid %1.")
.arg(targetUser.displayName)));
[] {
// do nothing
},
[channel, targetUser](auto error, auto message) {
auto errorMessage = formatStartRaidError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";
@ -182,36 +178,35 @@ QString cancelRaid(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /unraid command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /unraid command only works in Twitch channels.");
return "";
}
if (ctx.words.size() != 1)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: \"/unraid\" - Cancel the current raid. "
"Only the broadcaster can cancel the raid."));
ctx.channel->addSystemMessage(
"Usage: \"/unraid\" - Cancel the current raid. "
"Only the broadcaster can cancel the raid.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to cancel the raid!"));
ctx.channel->addSystemMessage(
"You must be logged in to cancel the raid!");
return "";
}
getHelix()->cancelRaid(
ctx.twitchChannel->roomId(),
[channel{ctx.channel}] {
channel->addMessage(
makeSystemMessage(QString("You cancelled the raid.")));
[] {
// do nothing
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatCancelRaidError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -1,14 +1,11 @@
#include "controllers/commands/builtin/twitch/RemoveModerator.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/Twitch.hpp"
namespace chatterino::commands {
@ -22,23 +19,23 @@ QString removeModerator(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /unmod command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /unmod command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/unmod <username>\" - Revoke moderator status from a "
"user. Use \"/mods\" to list the moderators of this channel."));
"user. Use \"/mods\" to list the moderators of this channel.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to unmod someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to unmod someone!");
return "";
}
@ -52,10 +49,10 @@ QString removeModerator(const CommandContext &ctx)
getHelix()->removeChannelModerator(
twitchChannel->roomId(), targetUser.id,
[channel, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You have removed %1 as a moderator of "
"this channel.")
.arg(targetUser.displayName)));
.arg(targetUser.displayName));
},
[channel, targetUser](auto error, auto message) {
QString errorMessage =
@ -107,13 +104,13 @@ QString removeModerator(const CommandContext &ctx)
}
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";

View file

@ -8,7 +8,6 @@
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "util/Twitch.hpp"
namespace chatterino::commands {
@ -22,23 +21,23 @@ QString removeVIP(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /unvip command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /unvip command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
"Usage: \"/unvip <username>\" - Revoke VIP status from a user. "
"Use \"/vips\" to list the VIPs of this channel."));
"Use \"/vips\" to list the VIPs of this channel.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to UnVIP someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to UnVIP someone!");
return "";
}
@ -52,9 +51,9 @@ QString removeVIP(const CommandContext &ctx)
getHelix()->removeChannelVIP(
twitchChannel->roomId(), targetUser.id,
[channel, targetUser] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("You have removed %1 as a VIP of this channel.")
.arg(targetUser.displayName)));
.arg(targetUser.displayName));
},
[channel, targetUser](auto error, auto message) {
QString errorMessage = QString("Failed to remove VIP - ");
@ -97,13 +96,13 @@ QString removeVIP(const CommandContext &ctx)
}
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";

View file

@ -1,9 +1,7 @@
#include "controllers/commands/builtin/twitch/SendReply.hpp"
#include "common/Channel.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageThread.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "util/Twitch.hpp"
@ -19,15 +17,14 @@ QString sendReply(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /reply command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /reply command only works in Twitch channels.");
return "";
}
if (ctx.words.size() < 3)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /reply <username> <message>"));
ctx.channel->addSystemMessage("Usage: /reply <username> <message>");
return "";
}
@ -54,8 +51,7 @@ QString sendReply(const CommandContext &ctx)
}
}
ctx.channel->addMessage(
makeSystemMessage("A message from that user wasn't found."));
ctx.channel->addSystemMessage("A message from that user wasn't found.");
return "";
}

View file

@ -9,8 +9,6 @@
#include "messages/MessageElement.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/irc/IrcChannel2.hpp"
#include "providers/irc/IrcServer.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchIrcServer.hpp"
@ -92,7 +90,7 @@ QString formatWhisperError(HelixWhisperError error, const QString &message)
bool appendWhisperMessageWordsLocally(const QStringList &words)
{
auto *app = getIApp();
auto *app = getApp();
MessageBuilder b;
@ -102,7 +100,7 @@ bool appendWhisperMessageWordsLocally(const QStringList &words)
MessageElementFlag::Text, MessageColor::Text,
FontStyle::ChatMediumBold);
b.emplace<TextElement>("->", MessageElementFlag::Text,
getIApp()->getThemes()->messages.textColors.system);
getApp()->getThemes()->messages.textColors.system);
b.emplace<TextElement>(words[1] + ":", MessageElementFlag::Text,
MessageColor::Text, FontStyle::ChatMediumBold);
@ -115,8 +113,8 @@ bool appendWhisperMessageWordsLocally(const QStringList &words)
for (int i = 2; i < words.length(); i++)
{
{ // Twitch emote
auto it = accemotes.emotes.find({words[i]});
if (it != accemotes.emotes.end())
auto it = accemotes->find({words[i]});
if (it != accemotes->end())
{
b.emplace<EmoteElement>(it->second,
MessageElementFlag::TwitchEmote);
@ -152,10 +150,10 @@ bool appendWhisperMessageWordsLocally(const QStringList &words)
void operator()(const QString &string,
MessageBuilder &b) const
{
LinkParser parser(string);
if (parser.result())
auto link = linkparser::parse(string);
if (link)
{
b.addLink(*parser.result());
b.addLink(*link, string);
}
else
{
@ -177,18 +175,15 @@ bool appendWhisperMessageWordsLocally(const QStringList &words)
b->flags.set(MessageFlag::Whisper);
auto messagexD = b.release();
getIApp()->getTwitch()->getWhispersChannel()->addMessage(messagexD);
auto overrideFlags = std::optional<MessageFlags>(messagexD->flags);
overrideFlags->set(MessageFlag::DoNotLog);
getApp()->getTwitch()->getWhispersChannel()->addMessage(
messagexD, MessageContext::Original);
if (getSettings()->inlineWhispers &&
!(getSettings()->streamerModeSuppressInlineWhispers &&
getIApp()->getStreamerMode()->isEnabled()))
getApp()->getStreamerMode()->isEnabled()))
{
app->getTwitchAbstract()->forEachChannel(
[&messagexD, overrideFlags](ChannelPtr _channel) {
_channel->addMessage(messagexD, overrideFlags);
app->getTwitch()->forEachChannel([&messagexD](ChannelPtr _channel) {
_channel->addMessage(messagexD, MessageContext::Repost);
});
}
@ -208,16 +203,15 @@ QString sendWhisper(const CommandContext &ctx)
if (ctx.words.size() < 3)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /w <username> <message>"));
ctx.channel->addSystemMessage("Usage: /w <username> <message>");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to send a whisper!"));
ctx.channel->addSystemMessage(
"You must be logged in to send a whisper!");
return "";
}
auto target = ctx.words.at(1);
@ -236,27 +230,15 @@ QString sendWhisper(const CommandContext &ctx)
},
[channel, target, targetUser](auto error, auto message) {
auto errorMessage = formatWhisperError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}] {
channel->addMessage(
makeSystemMessage("No user matching that username."));
channel->addSystemMessage("No user matching that username.");
});
return "";
}
// we must be on IRC
auto *ircChannel = dynamic_cast<IrcChannel *>(ctx.channel.get());
if (ircChannel == nullptr)
{
// give up
return "";
}
auto *server = ircChannel->server();
server->sendWhisper(target, message);
return "";
}

View file

@ -3,7 +3,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -17,20 +16,20 @@ QString toggleShieldMode(const CommandContext &ctx, bool isActivating)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QStringLiteral("The %1 command only works in Twitch channels.")
.arg(command)));
.arg(command));
return {};
}
auto user = getIApp()->getAccounts()->twitch.getCurrent();
auto user = getApp()->getAccounts()->twitch.getCurrent();
// Avoid Helix calls without Client ID and/or OAuth Token
if (user->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QStringLiteral("You must be logged in to use the %1 command.")
.arg(command)));
.arg(command));
return {};
}
@ -39,13 +38,11 @@ QString toggleShieldMode(const CommandContext &ctx, bool isActivating)
[channel = ctx.channel](const auto &res) {
if (!res.isActive)
{
channel->addMessage(
makeSystemMessage("Shield mode was deactivated."));
channel->addSystemMessage("Shield mode was deactivated.");
return;
}
channel->addMessage(
makeSystemMessage("Shield mode was activated."));
channel->addSystemMessage("Shield mode was activated.");
},
[channel = ctx.channel](const auto error, const auto &message) {
using Error = HelixUpdateShieldModeError;
@ -78,7 +75,7 @@ QString toggleShieldMode(const CommandContext &ctx, bool isActivating)
}
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return {};

View file

@ -3,7 +3,6 @@
#include "Application.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -19,24 +18,22 @@ QString sendShoutout(const CommandContext &ctx)
if (twitchChannel == nullptr)
{
channel->addMessage(makeSystemMessage(
"The /shoutout command only works in Twitch channels."));
channel->addSystemMessage(
"The /shoutout command only works in Twitch channels.");
return "";
}
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
channel->addMessage(
makeSystemMessage("You must be logged in to send shoutout."));
channel->addSystemMessage("You must be logged in to send shoutout.");
return "";
}
if (words->size() < 2)
{
channel->addMessage(
makeSystemMessage("Usage: \"/shoutout <username>\" - Sends a "
"shoutout to the specified twitch user"));
channel->addSystemMessage("Usage: \"/shoutout <username>\" - Sends a "
"shoutout to the specified twitch user");
return "";
}
@ -52,8 +49,8 @@ QString sendShoutout(const CommandContext &ctx)
twitchChannel->roomId(), targetUser.id,
currentUser->getUserId(),
[channel, targetUser]() {
channel->addMessage(makeSystemMessage(
QString("Sent shoutout to %1").arg(targetUser.login)));
channel->addSystemMessage(
QString("Sent shoutout to %1").arg(targetUser.login));
},
[channel](auto error, auto message) {
QString errorMessage = "Failed to send shoutout - ";
@ -99,13 +96,13 @@ QString sendShoutout(const CommandContext &ctx)
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel, target] {
// Equivalent error from IRC
channel->addMessage(
makeSystemMessage(QString("Invalid username: %1").arg(target)));
channel->addSystemMessage(
QString("Invalid username: %1").arg(target));
});
return "";

View file

@ -1,14 +1,11 @@
#include "controllers/commands/builtin/twitch/StartCommercial.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchMessageBuilder.hpp"
namespace {
@ -83,8 +80,8 @@ QString startCommercial(const CommandContext &ctx)
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(makeSystemMessage(
"The /commercial command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /commercial command only works in Twitch channels.");
return "";
}
@ -96,17 +93,17 @@ QString startCommercial(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(makeSystemMessage(usageStr));
ctx.channel->addSystemMessage(usageStr);
return "";
}
auto user = getIApp()->getAccounts()->twitch.getCurrent();
auto user = getApp()->getAccounts()->twitch.getCurrent();
// Avoid Helix calls without Client ID and/or OAuth Token
if (user->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
"You must be logged in to use the /commercial command."));
ctx.channel->addSystemMessage(
"You must be logged in to use the /commercial command.");
return "";
}
@ -116,18 +113,18 @@ QString startCommercial(const CommandContext &ctx)
getHelix()->startCommercial(
broadcasterID, length,
[channel{ctx.channel}](auto response) {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Starting %1 second long commercial break. "
"Keep in mind you are still "
"live and not all viewers will receive a "
"commercial. "
"You may run another commercial in %2 seconds.")
.arg(response.length)
.arg(response.retryAfter)));
.arg(response.retryAfter));
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage = formatStartCommercialError(error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -6,7 +6,6 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "controllers/commands/common/ChannelAction.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
@ -76,7 +75,7 @@ void unbanUserByID(const ChannelPtr &channel, const QString &channelID,
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
}
@ -96,7 +95,7 @@ QString unbanUser(const CommandContext &ctx)
{
if (ctx.channel != nullptr)
{
ctx.channel->addMessage(makeSystemMessage(actions.error()));
ctx.channel->addSystemMessage(actions.error());
}
else
{
@ -109,11 +108,11 @@ QString unbanUser(const CommandContext &ctx)
assert(!actions.value().empty());
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to unban someone!"));
ctx.channel->addSystemMessage(
"You must be logged in to unban someone!");
return "";
}
@ -159,16 +158,16 @@ QString unbanUser(const CommandContext &ctx)
userLoginsToFetch](const auto &users) mutable {
if (!actionChannel.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad channel name: %1")
.arg(actionChannel.login)));
.arg(actionChannel.login));
return;
}
if (!actionTarget.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad target name: %1")
.arg(actionTarget.login)));
.arg(actionTarget.login));
return;
}
@ -177,9 +176,9 @@ QString unbanUser(const CommandContext &ctx)
actionTarget.displayName);
},
[channel{ctx.channel}, userLoginsToFetch] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to timeout, bad username(s): %1")
.arg(userLoginsToFetch.join(", "))));
.arg(userLoginsToFetch.join(", ")));
});
}
else

View file

@ -1,9 +1,7 @@
#include "controllers/commands/builtin/twitch/UpdateChannel.hpp"
#include "common/Channel.hpp"
#include "common/network/NetworkResult.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@ -70,15 +68,14 @@ QString setTitle(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /settitle <stream title>"));
ctx.channel->addSystemMessage("Usage: /settitle <stream title>");
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(
makeSystemMessage("Unable to set title of non-Twitch channel."));
ctx.channel->addSystemMessage(
"Unable to set title of non-Twitch channel.");
return "";
}
@ -89,13 +86,13 @@ QString setTitle(const CommandContext &ctx)
[channel{ctx.channel}, title](const auto &result) {
(void)result;
channel->addMessage(
makeSystemMessage(QString("Updated title to %1").arg(title)));
channel->addSystemMessage(
QString("Updated title to %1").arg(title));
},
[channel{ctx.channel}](auto error, auto message) {
auto errorMessage =
formatUpdateChannelError("title", error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";
@ -110,15 +107,14 @@ QString setGame(const CommandContext &ctx)
if (ctx.words.size() < 2)
{
ctx.channel->addMessage(
makeSystemMessage("Usage: /setgame <stream game>"));
ctx.channel->addSystemMessage("Usage: /setgame <stream game>");
return "";
}
if (ctx.twitchChannel == nullptr)
{
ctx.channel->addMessage(
makeSystemMessage("Unable to set game of non-Twitch channel."));
ctx.channel->addSystemMessage(
"Unable to set game of non-Twitch channel.");
return "";
}
@ -130,7 +126,7 @@ QString setGame(const CommandContext &ctx)
gameName](const std::vector<HelixGame> &games) {
if (games.empty())
{
channel->addMessage(makeSystemMessage("Game not found."));
channel->addSystemMessage("Game not found.");
return;
}
@ -154,17 +150,17 @@ QString setGame(const CommandContext &ctx)
getHelix()->updateChannel(
twitchChannel->roomId(), matchedGame.id, "", "",
[channel, games, matchedGame](const NetworkResult &) {
channel->addMessage(makeSystemMessage(
QString("Updated game to %1").arg(matchedGame.name)));
channel->addSystemMessage(
QString("Updated game to %1").arg(matchedGame.name));
},
[channel](auto error, auto message) {
auto errorMessage =
formatUpdateChannelError("game", error, message);
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
},
[channel{ctx.channel}] {
channel->addMessage(makeSystemMessage("Failed to look up game."));
channel->addSystemMessage("Failed to look up game.");
});
return "";

View file

@ -21,17 +21,17 @@ QString updateUserColor(const CommandContext &ctx)
if (!ctx.channel->isTwitchChannel())
{
ctx.channel->addMessage(makeSystemMessage(
"The /color command only works in Twitch channels."));
ctx.channel->addSystemMessage(
"The /color command only works in Twitch channels.");
return "";
}
auto user = getIApp()->getAccounts()->twitch.getCurrent();
auto user = getApp()->getAccounts()->twitch.getCurrent();
// Avoid Helix calls without Client ID and/or OAuth Token
if (user->isAnon())
{
ctx.channel->addMessage(makeSystemMessage(
"You must be logged in to use the /color command."));
ctx.channel->addSystemMessage(
"You must be logged in to use the /color command.");
return "";
}
@ -39,11 +39,11 @@ QString updateUserColor(const CommandContext &ctx)
if (colorString.isEmpty())
{
ctx.channel->addMessage(makeSystemMessage(
ctx.channel->addSystemMessage(
QString("Usage: /color <color> - Color must be one of Twitch's "
"supported colors (%1) or a hex code (#000000) if you "
"have Turbo or Prime.")
.arg(VALID_HELIX_COLORS.join(", "))));
.arg(VALID_HELIX_COLORS.join(", ")));
return "";
}
@ -54,7 +54,7 @@ QString updateUserColor(const CommandContext &ctx)
[colorString, channel{ctx.channel}] {
QString successMessage =
QString("Your color has been changed to %1.").arg(colorString);
channel->addMessage(makeSystemMessage(successMessage));
channel->addSystemMessage(successMessage);
},
[colorString, channel{ctx.channel}](auto error, auto message) {
QString errorMessage =
@ -90,7 +90,7 @@ QString updateUserColor(const CommandContext &ctx)
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
return "";

View file

@ -1,14 +1,13 @@
#include "controllers/commands/builtin/twitch/Warn.hpp"
#include "Application.hpp"
#include "common/Channel.hpp"
#include "common/QLogging.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandContext.hpp"
#include "controllers/commands/common/ChannelAction.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/twitch/api/Helix.hpp"
#include "providers/twitch/TwitchAccount.hpp"
#include "providers/twitch/TwitchChannel.hpp"
namespace {
@ -73,7 +72,7 @@ void warnUserByID(const ChannelPtr &channel, const QString &channelID,
break;
}
channel->addMessage(makeSystemMessage(errorMessage));
channel->addSystemMessage(errorMessage);
});
}
@ -92,7 +91,7 @@ QString sendWarn(const CommandContext &ctx)
{
if (ctx.channel != nullptr)
{
ctx.channel->addMessage(makeSystemMessage(actions.error()));
ctx.channel->addSystemMessage(actions.error());
}
else
{
@ -105,11 +104,10 @@ QString sendWarn(const CommandContext &ctx)
assert(!actions.value().empty());
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
if (currentUser->isAnon())
{
ctx.channel->addMessage(
makeSystemMessage("You must be logged in to warn someone!"));
ctx.channel->addSystemMessage("You must be logged in to warn someone!");
return "";
}
@ -118,8 +116,8 @@ QString sendWarn(const CommandContext &ctx)
const auto &reason = action.reason;
if (reason.isEmpty())
{
ctx.channel->addMessage(
makeSystemMessage("Failed to warn, you must specify a reason"));
ctx.channel->addSystemMessage(
"Failed to warn, you must specify a reason");
break;
}
@ -161,16 +159,16 @@ QString sendWarn(const CommandContext &ctx)
userLoginsToFetch](const auto &users) mutable {
if (!actionChannel.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to warn, bad channel name: %1")
.arg(actionChannel.login)));
.arg(actionChannel.login));
return;
}
if (!actionTarget.hydrateFrom(users))
{
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to warn, bad target name: %1")
.arg(actionTarget.login)));
.arg(actionTarget.login));
return;
}
@ -179,9 +177,9 @@ QString sendWarn(const CommandContext &ctx)
reason, actionTarget.displayName);
},
[channel{ctx.channel}, userLoginsToFetch] {
channel->addMessage(makeSystemMessage(
channel->addSystemMessage(
QString("Failed to warn, bad username(s): %1")
.arg(userLoginsToFetch.join(", "))));
.arg(userLoginsToFetch.join(", ")));
});
}
else

View file

@ -39,7 +39,7 @@ void TabCompletionModel::updateResults(const QString &query,
// Try plugins first
bool done{};
std::tie(done, results) =
getIApp()->getPlugins()->updateCustomCompletions(
getApp()->getPlugins()->updateCustomCompletions(
query, fullTextContent, cursorPosition, isFirstWord);
if (done)
{

View file

@ -71,20 +71,20 @@ void CommandSource::initializeItems()
std::vector<CommandItem> commands;
#ifdef CHATTERINO_HAVE_PLUGINS
for (const auto &command : getIApp()->getCommands()->pluginCommands())
for (const auto &command : getApp()->getCommands()->pluginCommands())
{
addCommand(command, commands);
}
#endif
// Custom Chatterino commands
for (const auto &command : getIApp()->getCommands()->items)
for (const auto &command : getApp()->getCommands()->items)
{
addCommand(command.name, commands);
}
// Default Chatterino commands
auto x = getIApp()->getCommands()->getDefaultChatterinoCommandList();
auto x = getApp()->getCommands()->getDefaultChatterinoCommandList();
for (const auto &command : x)
{
addCommand(command, commands);

View file

@ -88,33 +88,23 @@ void EmoteSource::addToStringList(QStringList &list, size_t maxCount,
void EmoteSource::initializeFromChannel(const Channel *channel)
{
auto *app = getIApp();
auto *app = getApp();
std::vector<EmoteItem> emotes;
const auto *tc = dynamic_cast<const TwitchChannel *>(channel);
// returns true also for special Twitch channels (/live, /mentions, /whispers, etc.)
if (channel->isTwitchChannel())
{
if (auto user = app->getAccounts()->twitch.getCurrent())
{
// Twitch Emotes available globally
auto emoteData = user->accessEmotes();
addEmotes(emotes, emoteData->emotes, "Twitch Emote");
// Twitch Emotes available locally
auto localEmoteData = user->accessLocalEmotes();
if ((tc != nullptr) &&
localEmoteData->find(tc->roomId()) != localEmoteData->end())
{
if (const auto *localEmotes = &localEmoteData->at(tc->roomId()))
{
addEmotes(emotes, *localEmotes, "Local Twitch Emotes");
}
}
}
if (tc)
{
if (auto twitch = tc->localTwitchEmotes())
{
addEmotes(emotes, *twitch, "Local Twitch Emotes");
}
auto user = getApp()->getAccounts()->twitch.getCurrent();
addEmotes(emotes, **user->accessEmotes(), "Twitch Emote");
// TODO extract "Channel {BetterTTV,7TV,FrankerFaceZ}" text into a #define.
if (auto bttv = tc->bttvEmotes())
{

View file

@ -12,7 +12,7 @@ namespace chatterino::filters {
ContextMap buildContextMap(const MessagePtr &m, chatterino::Channel *channel)
{
auto watchingChannel = getIApp()->getTwitch()->getWatchingChannel().get();
auto watchingChannel = getApp()->getTwitch()->getWatchingChannel().get();
/*
* Looking to add a new identifier to filters? Here's what to do:

View file

@ -72,22 +72,35 @@ QString possibleTypeToString(const PossibleType &possible);
bool isList(const PossibleType &possibleType);
inline bool variantIs(const QVariant &a, QMetaType::Type type)
inline bool variantIs(const QVariant &a, int type)
{
return static_cast<QMetaType::Type>(a.type()) == type;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return a.typeId() == type;
#else
return a.type() == type;
#endif
}
inline bool variantIsNot(const QVariant &a, QMetaType::Type type)
inline bool variantIsNot(const QVariant &a, int type)
{
return static_cast<QMetaType::Type>(a.type()) != type;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return a.typeId() != type;
#else
return a.type() != type;
#endif
}
inline bool convertVariantTypes(QVariant &a, QVariant &b, int type)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QMetaType ty(type);
return a.convert(ty) && b.convert(ty);
#else
return a.convert(type) && b.convert(type);
#endif
}
inline bool variantTypesMatch(QVariant &a, QVariant &b, QMetaType::Type type)
inline bool variantTypesMatch(QVariant &a, QVariant &b, int type)
{
return variantIs(a, type) && variantIs(b, type);
}

View file

@ -56,9 +56,8 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
switch (this->op_)
{
case PLUS:
if (static_cast<QMetaType::Type>(left.type()) ==
QMetaType::QString &&
right.canConvert(QMetaType::QString))
if (variantIs(left, QMetaType::QString) &&
right.canConvert<QString>())
{
return left.toString().append(right.toString());
}
@ -143,14 +142,14 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return false;
case CONTAINS:
if (variantIs(left, QMetaType::QStringList) &&
right.canConvert(QMetaType::QString))
right.canConvert<QString>())
{
return left.toStringList().contains(right.toString(),
Qt::CaseInsensitive);
}
if (variantIs(left, QMetaType::QVariantMap) &&
right.canConvert(QMetaType::QString))
right.canConvert<QString>())
{
return left.toMap().contains(right.toString());
}
@ -160,8 +159,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return left.toList().contains(right);
}
if (left.canConvert(QMetaType::QString) &&
right.canConvert(QMetaType::QString))
if (left.canConvert<QString>() && right.canConvert<QString>())
{
return left.toString().contains(right.toString(),
Qt::CaseInsensitive);
@ -170,7 +168,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return false;
case STARTS_WITH:
if (variantIs(left, QMetaType::QStringList) &&
right.canConvert(QMetaType::QString))
right.canConvert<QString>())
{
auto list = left.toStringList();
return !list.isEmpty() &&
@ -183,8 +181,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return left.toList().startsWith(right);
}
if (left.canConvert(QMetaType::QString) &&
right.canConvert(QMetaType::QString))
if (left.canConvert<QString>() && right.canConvert<QString>())
{
return left.toString().startsWith(right.toString(),
Qt::CaseInsensitive);
@ -194,7 +191,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
case ENDS_WITH:
if (variantIs(left, QMetaType::QStringList) &&
right.canConvert(QMetaType::QString))
right.canConvert<QString>())
{
auto list = left.toStringList();
return !list.isEmpty() &&
@ -207,8 +204,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return left.toList().endsWith(right);
}
if (left.canConvert(QMetaType::QString) &&
right.canConvert(QMetaType::QString))
if (left.canConvert<QString>() && right.canConvert<QString>())
{
return left.toString().endsWith(right.toString(),
Qt::CaseInsensitive);
@ -216,14 +212,18 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
return false;
case MATCH: {
if (!left.canConvert(QMetaType::QString))
if (!left.canConvert<QString>())
{
return false;
}
auto matching = left.toString();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (static_cast<QMetaType::Type>(right.typeId()))
#else
switch (static_cast<QMetaType::Type>(right.type()))
#endif
{
case QMetaType::QRegularExpression: {
return right.toRegularExpression()

View file

@ -53,7 +53,7 @@ void BadgeHighlightModel::getRowFromItem(const HighlightBadge &item,
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
setColorItem(row[Column::Color], *item.getColor());
getIApp()->getTwitchBadges()->getBadgeIcon(
getApp()->getTwitchBadges()->getBadgeIcon(
item.badgeName(), [item, row](QString /*name*/, const QIconPtr pixmap) {
row[Column::Badge]->setData(QVariant(*pixmap), Qt::DecorationRole);
});

View file

@ -1,8 +1,7 @@
#include "HighlightBadge.hpp"
#include "controllers/highlights/HighlightBadge.hpp"
#include "messages/SharedMessageBuilder.hpp"
#include "providers/twitch/TwitchBadge.hpp"
#include "singletons/Resources.hpp"
#include "util/IrcHelpers.hpp"
namespace chatterino {
@ -97,7 +96,7 @@ bool HighlightBadge::compare(const QString &id, const Badge &badge) const
{
if (this->hasVersions_)
{
auto parts = SharedMessageBuilder::slashKeyValue(id);
auto parts = slashKeyValue(id);
return parts.first.compare(badge.key_, Qt::CaseInsensitive) == 0 &&
parts.second.compare(badge.value_, Qt::CaseInsensitive) == 0;
}

View file

@ -183,7 +183,7 @@ void rebuildReplyThreadHighlight(Settings &settings,
void rebuildMessageHighlights(Settings &settings,
std::vector<HighlightCheck> &checks)
{
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
QString currentUsername = currentUser->getUserName();
if (settings.enableSelfHighlight && !currentUsername.isEmpty() &&
@ -442,9 +442,11 @@ std::ostream &operator<<(std::ostream &os, const HighlightResult &result)
return os;
}
void HighlightController::initialize(Settings &settings,
const Paths & /*paths*/)
HighlightController::HighlightController(Settings &settings,
AccountController *accounts)
{
assert(accounts != nullptr);
this->rebuildListener_.addSetting(settings.enableSelfHighlight);
this->rebuildListener_.addSetting(settings.enableSelfHighlightSound);
this->rebuildListener_.addSetting(settings.enableSelfHighlightTaskbar);
@ -507,12 +509,12 @@ void HighlightController::initialize(Settings &settings,
this->rebuildChecks(settings);
});
getIApp()->getAccounts()->twitch.currentUserChanged.connect(
[this, &settings] {
this->bConnections.emplace_back(
accounts->twitch.currentUserChanged.connect([this, &settings] {
qCDebug(chatterinoHighlights)
<< "Rebuild checks because user swapped accounts";
this->rebuildChecks(settings);
});
}));
this->rebuildChecks(settings);
}
@ -550,7 +552,7 @@ std::pair<bool, HighlightResult> HighlightController::check(
// Access for checking
const auto checks = this->checks_.accessConst();
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
auto currentUser = getApp()->getAccounts()->twitch.getCurrent();
auto self = (senderName == currentUser->getUserName());
for (const auto &check : *checks)

View file

@ -1,9 +1,10 @@
#pragma once
#include "common/FlagsEnum.hpp"
#include "common/Singleton.hpp"
#include "common/UniqueAccess.hpp"
#include "messages/MessageFlag.hpp"
#include "singletons/Settings.hpp"
#include <boost/signals2/connection.hpp>
#include <pajlada/settings.hpp>
#include <pajlada/settings/settinglistener.hpp>
#include <QColor>
@ -18,8 +19,7 @@ namespace chatterino {
class Badge;
struct MessageParseArgs;
enum class MessageFlag : int64_t;
using MessageFlags = FlagsEnum<MessageFlag>;
class AccountController;
struct HighlightResult {
HighlightResult(bool _alert, bool _playSound,
@ -83,10 +83,10 @@ struct HighlightCheck {
Checker cb;
};
class HighlightController final : public Singleton
class HighlightController final
{
public:
void initialize(Settings &settings, const Paths &paths) override;
HighlightController(Settings &settings, AccountController *accounts);
/**
* @brief Checks the given message parameters if it matches our internal checks, and returns a result
@ -108,6 +108,7 @@ private:
pajlada::SettingListener rebuildListener_;
pajlada::Signals::SignalHolder signalHolder_;
std::vector<boost::signals2::scoped_connection> bConnections;
};
} // namespace chatterino

View file

@ -519,7 +519,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
break;
}
getIApp()->getWindows()->forceLayoutChannelViews();
getApp()->getWindows()->forceLayoutChannelViews();
}
} // namespace chatterino

View file

@ -109,7 +109,7 @@ void UserHighlightModel::customRowSetData(
break;
}
getIApp()->getWindows()->forceLayoutChannelViews();
getApp()->getWindows()->forceLayoutChannelViews();
}
// row into vector item

Some files were not shown because too many files have changed in this diff Show more