mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Merge branch 'master' of github.com:Chatterino/chatterino2 into chore/eradicate_hardcoded_names_from_our_amazing_codebase
This commit is contained in:
commit
d38caafcbc
222 changed files with 8851 additions and 3057 deletions
47
.CI/build-installer.ps1
Normal file
47
.CI/build-installer.ps1
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
if (-not (Test-Path -PathType Container Chatterino2)) {
|
||||||
|
Write-Error "Couldn't find a folder called 'Chatterino2' in the current directory.";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we're on a tag
|
||||||
|
$OldErrorActionPref = $ErrorActionPreference;
|
||||||
|
$ErrorActionPreference = 'Continue';
|
||||||
|
git describe --exact-match --match 'v*' *> $null;
|
||||||
|
$isTagged = $?;
|
||||||
|
$ErrorActionPreference = $OldErrorActionPref;
|
||||||
|
|
||||||
|
$defines = $null;
|
||||||
|
if ($isTagged) {
|
||||||
|
# This is a release.
|
||||||
|
# Make sure, any existing `modes` file is overwritten for the user,
|
||||||
|
# for example when updating from nightly to stable.
|
||||||
|
Write-Output "" > Chatterino2/modes;
|
||||||
|
$installerBaseName = "Chatterino.Installer";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output nightly > Chatterino2/modes;
|
||||||
|
$defines = "/DIS_NIGHTLY=1";
|
||||||
|
$installerBaseName = "Chatterino.Nightly.Installer";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Env:GITHUB_OUTPUT) {
|
||||||
|
# This is used in CI when creating the artifact
|
||||||
|
"C2_INSTALLER_BASE_NAME=$installerBaseName" >> "$Env:GITHUB_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy vc_redist.x64.exe
|
||||||
|
if ($null -eq $Env:VCToolsRedistDir) {
|
||||||
|
Write-Error "VCToolsRedistDir is not set. Forgot to set Visual Studio environment variables?";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Copy-Item "$Env:VCToolsRedistDir\vc_redist.x64.exe" .;
|
||||||
|
|
||||||
|
# Build the installer
|
||||||
|
ISCC `
|
||||||
|
/DWORKING_DIR="$($pwd.Path)\" `
|
||||||
|
/DINSTALLER_BASE_NAME="$installerBaseName" `
|
||||||
|
$defines `
|
||||||
|
/O. `
|
||||||
|
"$PSScriptRoot\chatterino-installer.iss";
|
||||||
|
|
||||||
|
Move-Item "$installerBaseName.exe" "$installerBaseName$($Env:VARIANT_SUFFIX).exe"
|
87
.CI/chatterino-installer.iss
Normal file
87
.CI/chatterino-installer.iss
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "Chatterino"
|
||||||
|
#define MyAppVersion "2.4.4"
|
||||||
|
#define MyAppPublisher "Chatterino Team"
|
||||||
|
#define MyAppURL "https://www.chatterino.com"
|
||||||
|
#define MyAppExeName "chatterino.exe"
|
||||||
|
|
||||||
|
; used in build-installer.ps1
|
||||||
|
; if set, must end in a backslash
|
||||||
|
#ifndef WORKING_DIR
|
||||||
|
#define WORKING_DIR ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId={{F5FE6614-04D4-4D32-8600-0ABA0AC113A4}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={autopf}\{#MyAppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
ArchitecturesInstallIn64BitMode=x64
|
||||||
|
;Uncomment the following line to run in non administrative install mode (install for current user only.)
|
||||||
|
;PrivilegesRequired=lowest
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
OutputDir=out
|
||||||
|
; This is defined by the build-installer.ps1 script,
|
||||||
|
; but kept optional for regular use.
|
||||||
|
#ifdef INSTALLER_BASE_NAME
|
||||||
|
OutputBaseFilename={#INSTALLER_BASE_NAME}
|
||||||
|
#else
|
||||||
|
OutputBaseFilename=Chatterino.Installer
|
||||||
|
#endif
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
UsePreviousTasks=no
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
RestartIfNeededByRun=no
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
#ifdef IS_NIGHTLY
|
||||||
|
[Messages]
|
||||||
|
SetupAppTitle=Setup (Nightly)
|
||||||
|
SetupWindowTitle=Setup - %1 (Nightly)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "vcredist"; Description: "Install the required Visual C++ 2015/2017/2019/2022 Redistributable";
|
||||||
|
; GroupDescription: "{cm:AdditionalIcons}";
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; Flags: unchecked
|
||||||
|
Name: "freshinstall"; Description: "Fresh install (delete old settings/logs)"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "{#WORKING_DIR}Chatterino2\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "{#WORKING_DIR}vc_redist.x64.exe"; DestDir: "{tmp}"; Tasks: vcredist;
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
; VC++ redistributable
|
||||||
|
Filename: {tmp}\vc_redist.x64.exe; Parameters: "/install /passive /norestart"; StatusMsg: "Installing 64-bit Windows Universal Runtime..."; Flags: waituntilterminated; Tasks: vcredist
|
||||||
|
; Run chatterino
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[InstallDelete]
|
||||||
|
; Delete cache on install
|
||||||
|
Type: filesandordirs; Name: "{userappdata}\Chatterino2\Cache"
|
||||||
|
; Delete %appdata%\Chatterino2 on freshinstall
|
||||||
|
Type: filesandordirs; Name: "{userappdata}\Chatterino2"; Tasks: freshinstall
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
; Delete cache on uninstall
|
||||||
|
Type: filesandordirs; Name: "{userappdata}\Chatterino2\Cache"
|
|
@ -18,6 +18,7 @@ Checks: "-*,
|
||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
-cppcoreguidelines-owning-memory,
|
-cppcoreguidelines-owning-memory,
|
||||||
-cppcoreguidelines-avoid-magic-numbers,
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
|
-cppcoreguidelines-avoid-const-or-ref-data-members,
|
||||||
-readability-magic-numbers,
|
-readability-magic-numbers,
|
||||||
-performance-noexcept-move-constructor,
|
-performance-noexcept-move-constructor,
|
||||||
-misc-non-private-member-variables-in-classes,
|
-misc-non-private-member-variables-in-classes,
|
||||||
|
@ -49,6 +50,8 @@ CheckOptions:
|
||||||
value: CamelCase
|
value: CamelCase
|
||||||
- key: readability-identifier-naming.GlobalConstantCase
|
- key: readability-identifier-naming.GlobalConstantCase
|
||||||
value: UPPER_CASE
|
value: UPPER_CASE
|
||||||
|
- key: readability-identifier-naming.GlobalVariableCase
|
||||||
|
value: UPPER_CASE
|
||||||
- key: readability-identifier-naming.VariableCase
|
- key: readability-identifier-naming.VariableCase
|
||||||
value: camelBack
|
value: camelBack
|
||||||
- key: readability-implicit-bool-conversion.AllowPointerConditions
|
- key: readability-implicit-bool-conversion.AllowPointerConditions
|
||||||
|
|
14
.git-blame-ignore-revs
Normal file
14
.git-blame-ignore-revs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# If a commit modifies a ton of files and doesn't really contribute to the
|
||||||
|
# output of git-blame, please add it here
|
||||||
|
#
|
||||||
|
# Don't add commits from the same PR you are creating. We squash PRs into a
|
||||||
|
# single commit, so references to those commits will be lost
|
||||||
|
#
|
||||||
|
# 2018 - changed to 80 max column
|
||||||
|
f71ff08e686ae76c3dd4084d0f05f27ba9b3fdcb
|
||||||
|
#
|
||||||
|
# 2018 - added brace wrapping after if and for
|
||||||
|
e259b9e39f46f3cb0e4838c988d4f320a03dfaa4
|
||||||
|
#
|
||||||
|
# 2019 - Normalize line endings in already existing files
|
||||||
|
b06eb9df835c25154899fbcf43e9b37addcea1b1
|
48
.github/workflows/build.yml
vendored
48
.github/workflows/build.yml
vendored
|
@ -7,6 +7,7 @@ on:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build-${{ github.ref }}
|
group: build-${{ github.ref }}
|
||||||
|
@ -107,7 +108,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install Qt5
|
- name: Install Qt5
|
||||||
if: startsWith(matrix.qt-version, '5.')
|
if: startsWith(matrix.qt-version, '5.')
|
||||||
uses: jurplel/install-qt-action@v3.2.0
|
uses: jurplel/install-qt-action@v3.2.1
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
|
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
|
||||||
|
@ -115,7 +116,7 @@ jobs:
|
||||||
|
|
||||||
- name: Install Qt6
|
- name: Install Qt6
|
||||||
if: startsWith(matrix.qt-version, '6.')
|
if: startsWith(matrix.qt-version, '6.')
|
||||||
uses: jurplel/install-qt-action@v3.2.0
|
uses: jurplel/install-qt-action@v3.2.1
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
|
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
|
||||||
|
@ -134,6 +135,18 @@ jobs:
|
||||||
"C2_CONAN_CACHE_SUFFIX=$(if ($Env:C2_BUILD_WITH_QT6 -eq "on") { "-QT6" } else { "`" })" >> "$Env:GITHUB_ENV"
|
"C2_CONAN_CACHE_SUFFIX=$(if ($Env:C2_BUILD_WITH_QT6 -eq "on") { "-QT6" } else { "`" })" >> "$Env:GITHUB_ENV"
|
||||||
shell: powershell
|
shell: powershell
|
||||||
|
|
||||||
|
- name: Setup sccache (Windows)
|
||||||
|
# sccache v0.5.3
|
||||||
|
uses: nerixyz/ccache-action@9a7e8d00116ede600ee7717350c6594b8af6aaa5
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
with:
|
||||||
|
variant: sccache
|
||||||
|
# only save on on the default (master) branch
|
||||||
|
save: ${{ github.event_name == 'push' }}
|
||||||
|
key: sccache-build-${{ matrix.os }}-${{ matrix.qt-version }}-${{ matrix.skip-crashpad }}
|
||||||
|
restore-keys: |
|
||||||
|
sccache-build-${{ matrix.os }}-${{ matrix.qt-version }}
|
||||||
|
|
||||||
- name: Cache conan packages (Windows)
|
- name: Cache conan packages (Windows)
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows')
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
@ -171,13 +184,16 @@ jobs:
|
||||||
- name: Build (Windows)
|
- name: Build (Windows)
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows')
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
# Enable PCH on Windows when crashpad is enabled
|
||||||
|
C2_WINDOWS_USE_PCH: ${{ matrix.skip-crashpad && 'OFF' || 'ON' }}
|
||||||
run: |
|
run: |
|
||||||
cd build
|
cd build
|
||||||
cmake `
|
cmake `
|
||||||
-G"NMake Makefiles" `
|
-G"NMake Makefiles" `
|
||||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||||
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" `
|
-DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" `
|
||||||
-DUSE_PRECOMPILED_HEADERS=OFF `
|
-DUSE_PRECOMPILED_HEADERS=${{ env.C2_WINDOWS_USE_PCH }} `
|
||||||
-DBUILD_WITH_CRASHPAD="$Env:C2_ENABLE_CRASHPAD" `
|
-DBUILD_WITH_CRASHPAD="$Env:C2_ENABLE_CRASHPAD" `
|
||||||
-DCHATTERINO_LTO="$Env:C2_ENABLE_LTO" `
|
-DCHATTERINO_LTO="$Env:C2_ENABLE_LTO" `
|
||||||
-DCHATTERINO_PLUGINS="$Env:C2_PLUGINS" `
|
-DCHATTERINO_PLUGINS="$Env:C2_PLUGINS" `
|
||||||
|
@ -276,16 +292,34 @@ jobs:
|
||||||
|
|
||||||
- name: clang-tidy review
|
- name: clang-tidy review
|
||||||
if: matrix.clang-tidy-review && github.event_name == 'pull_request'
|
if: matrix.clang-tidy-review && github.event_name == 'pull_request'
|
||||||
uses: ZedThree/clang-tidy-review@v0.13.0
|
uses: ZedThree/clang-tidy-review@v0.13.2
|
||||||
with:
|
with:
|
||||||
build_dir: build
|
build_dir: build-clang-tidy
|
||||||
config_file: ".clang-tidy"
|
config_file: ".clang-tidy"
|
||||||
split_workflow: true
|
split_workflow: true
|
||||||
exclude: "tests/*,lib/*"
|
exclude: "lib/*"
|
||||||
|
cmake_command: >-
|
||||||
|
cmake -S. -Bbuild-clang-tidy
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
-DPAJLADA_SETTINGS_USE_BOOST_FILESYSTEM=On
|
||||||
|
-DUSE_PRECOMPILED_HEADERS=OFF
|
||||||
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=On
|
||||||
|
-DCHATTERINO_LTO=Off
|
||||||
|
-DCHATTERINO_PLUGINS=On
|
||||||
|
-DBUILD_WITH_QT6=Off
|
||||||
|
-DBUILD_TESTS=On
|
||||||
|
-DBUILD_BENCHMARKS=On
|
||||||
|
apt_packages: >-
|
||||||
|
qttools5-dev, qt5-image-formats-plugins, libqt5svg5-dev,
|
||||||
|
libsecret-1-dev,
|
||||||
|
libboost-dev, libboost-system-dev, libboost-filesystem-dev,
|
||||||
|
libssl-dev,
|
||||||
|
rapidjson-dev,
|
||||||
|
libbenchmark-dev
|
||||||
|
|
||||||
- name: clang-tidy-review upload
|
- name: clang-tidy-review upload
|
||||||
if: matrix.clang-tidy-review && github.event_name == 'pull_request'
|
if: matrix.clang-tidy-review && github.event_name == 'pull_request'
|
||||||
uses: ZedThree/clang-tidy-review/upload@v0.13.0
|
uses: ZedThree/clang-tidy-review/upload@v0.13.2
|
||||||
|
|
||||||
- name: Package - AppImage (Ubuntu)
|
- name: Package - AppImage (Ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu-20.04') && !matrix.skip-artifact
|
if: startsWith(matrix.os, 'ubuntu-20.04') && !matrix.skip-artifact
|
||||||
|
|
1
.github/workflows/check-formatting.yml
vendored
1
.github/workflows/check-formatting.yml
vendored
|
@ -6,6 +6,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: check-formatting-${{ github.ref }}
|
group: check-formatting-${{ github.ref }}
|
||||||
|
|
56
.github/workflows/create-installer.yml
vendored
Normal file
56
.github/workflows/create-installer.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: Create installer
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Build"]
|
||||||
|
types: [completed]
|
||||||
|
# make sure this only runs on the default branch
|
||||||
|
branches: [master]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-installer:
|
||||||
|
runs-on: windows-latest
|
||||||
|
# Only run manually or when a build succeeds
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
qt-version: [5.15.2, 6.5.0]
|
||||||
|
env:
|
||||||
|
VARIANT_SUFFIX: ${{ startsWith(matrix.qt-version, '6.') && '.EXPERIMENTAL-Qt6' || '' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # allows for tags access
|
||||||
|
|
||||||
|
- name: Download artifact
|
||||||
|
uses: dawidd6/action-download-artifact@v2
|
||||||
|
with:
|
||||||
|
workflow: build.yml
|
||||||
|
name: chatterino-windows-x86-64-Qt-${{ matrix.qt-version }}.zip
|
||||||
|
path: build/
|
||||||
|
|
||||||
|
- name: Unzip
|
||||||
|
run: 7z e -spf chatterino-windows-x86-64-Qt-${{ matrix.qt-version }}.zip
|
||||||
|
working-directory: build
|
||||||
|
|
||||||
|
- name: Install InnoSetup
|
||||||
|
run: choco install innosetup
|
||||||
|
|
||||||
|
- name: Add InnoSetup to path
|
||||||
|
run: echo "C:\Program Files (x86)\Inno Setup 6\" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||||
|
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.12.1
|
||||||
|
|
||||||
|
- name: Build installer
|
||||||
|
id: build-installer
|
||||||
|
working-directory: build
|
||||||
|
run: ..\.CI\build-installer.ps1
|
||||||
|
shell: powershell
|
||||||
|
|
||||||
|
- name: Upload installer
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
path: build/${{ steps.build-installer.outputs.C2_INSTALLER_BASE_NAME }}${{ env.VARIANT_SUFFIX }}.exe
|
||||||
|
name: ${{ steps.build-installer.outputs.C2_INSTALLER_BASE_NAME }}${{ env.VARIANT_SUFFIX }}.exe
|
3
.github/workflows/lint.yml
vendored
3
.github/workflows/lint.yml
vendored
|
@ -6,6 +6,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: lint-${{ github.ref }}
|
group: lint-${{ github.ref }}
|
||||||
|
@ -19,7 +20,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Check formatting with Prettier
|
- name: Check formatting with Prettier
|
||||||
uses: actionsx/prettier@e90ec5455552f0f640781bdd5f5d2415acb52f1a
|
uses: actionsx/prettier@3d9f7c3fa44c9cb819e68292a328d7f4384be206
|
||||||
with:
|
with:
|
||||||
# prettier CLI arguments.
|
# prettier CLI arguments.
|
||||||
args: --write .
|
args: --write .
|
||||||
|
|
2
.github/workflows/post-clang-tidy-review.yml
vendored
2
.github/workflows/post-clang-tidy-review.yml
vendored
|
@ -12,6 +12,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: ZedThree/clang-tidy-review/post@v0.13.0
|
- uses: ZedThree/clang-tidy-review/post@v0.13.2
|
||||||
with:
|
with:
|
||||||
lgtm_comment_body: ""
|
lgtm_comment_body: ""
|
||||||
|
|
38
.github/workflows/test.yml
vendored
38
.github/workflows/test.yml
vendored
|
@ -4,9 +4,11 @@ name: Test
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
merge_group:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TWITCH_PUBSUB_SERVER_IMAGE: ghcr.io/chatterino/twitch-pubsub-server-test:v1.0.6
|
TWITCH_PUBSUB_SERVER_IMAGE: ghcr.io/chatterino/twitch-pubsub-server-test:v1.0.6
|
||||||
|
QT_QPA_PLATFORM: minimal
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: test-${{ github.ref }}
|
group: test-${{ github.ref }}
|
||||||
|
@ -17,29 +19,30 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04]
|
include:
|
||||||
qt-version: [5.15.2]
|
- os: "ubuntu-20.04"
|
||||||
|
qt-version: "5.15.2"
|
||||||
|
- os: "ubuntu-20.04"
|
||||||
|
qt-version: "5.12.12"
|
||||||
|
- os: "ubuntu-22.04"
|
||||||
|
qt-version: "6.2.4"
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
env:
|
||||||
|
C2_BUILD_WITH_QT6: ${{ startsWith(matrix.qt-version, '6.') && 'ON' || 'OFF' }}
|
||||||
|
QT_MODULES: ${{ startsWith(matrix.qt-version, '6.') && 'qt5compat qtimageformats' || '' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Cache Qt
|
|
||||||
id: cache-qt
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: "${{ github.workspace }}/qt/"
|
|
||||||
key: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}
|
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3.2.0
|
uses: jurplel/install-qt-action@v3.2.1
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}
|
cache-key-prefix: ${{ runner.os }}-QtCache-${{ matrix.qt-version }}-v2
|
||||||
|
modules: ${{ env.QT_MODULES }}
|
||||||
version: ${{ matrix.qt-version }}
|
version: ${{ matrix.qt-version }}
|
||||||
dir: "${{ github.workspace }}/qt/"
|
|
||||||
|
|
||||||
# LINUX
|
# LINUX
|
||||||
- name: Install dependencies (Ubuntu)
|
- name: Install dependencies (Ubuntu)
|
||||||
|
@ -73,18 +76,23 @@ jobs:
|
||||||
- name: Build (Ubuntu)
|
- name: Build (Ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
cmake -DBUILD_TESTS=On -DBUILD_APP=OFF ..
|
cmake \
|
||||||
cmake --build . --config Release
|
-DBUILD_TESTS=On \
|
||||||
|
-DBUILD_APP=OFF \
|
||||||
|
-DBUILD_WITH_QT6="$C2_BUILD_WITH_QT6" \
|
||||||
|
..
|
||||||
|
cmake --build .
|
||||||
working-directory: build-test
|
working-directory: build-test
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Test (Ubuntu)
|
- name: Test (Ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
timeout-minutes: 30
|
||||||
run: |
|
run: |
|
||||||
docker pull kennethreitz/httpbin
|
docker pull kennethreitz/httpbin
|
||||||
docker pull ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
|
docker pull ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
|
||||||
docker run --network=host --detach ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
|
docker run --network=host --detach ${{ env.TWITCH_PUBSUB_SERVER_IMAGE }}
|
||||||
docker run -p 9051:80 --detach kennethreitz/httpbin
|
docker run -p 9051:80 --detach kennethreitz/httpbin
|
||||||
./bin/chatterino-test --platform minimal || ./bin/chatterino-test --platform minimal || ./bin/chatterino-test --platform minimal
|
./bin/chatterino-test || ./bin/chatterino-test || ./bin/chatterino-test
|
||||||
working-directory: build-test
|
working-directory: build-test
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -121,3 +121,6 @@ resources/resources_autogenerated.qrc
|
||||||
|
|
||||||
# Leftovers from running `aqt install`
|
# Leftovers from running `aqt install`
|
||||||
aqtinstall.log
|
aqtinstall.log
|
||||||
|
|
||||||
|
# sccache (CI)
|
||||||
|
.sccache
|
||||||
|
|
61
CHANGELOG.md
61
CHANGELOG.md
|
@ -2,6 +2,65 @@
|
||||||
|
|
||||||
## Unversioned
|
## Unversioned
|
||||||
|
|
||||||
|
- Minor: Message input is now focused when clicking on emotes. (#4719)
|
||||||
|
- Minor: Changed viewer list to chatter list to more match Twitch's terminology. (#4732)
|
||||||
|
- Minor: Nicknames are now taken into consideration when searching for messages. (#4663, #4742)
|
||||||
|
- Minor: Add an icon showing when streamer mode is enabled (#4410, #4690)
|
||||||
|
- Minor: Added `/shoutout <username>` commands to shoutout specified user. (#4638)
|
||||||
|
- Minor: Improved editing hotkeys. (#4628)
|
||||||
|
- Minor: The input completion and quick switcher are now styled to match your theme. (#4671)
|
||||||
|
- Minor: Added setting to only show tabs with live channels (default toggle hotkey: Ctrl+Shift+L). (#4358)
|
||||||
|
- Minor: Added better support for Twitch's Hype Chat feature. (#4715)
|
||||||
|
- Minor: Added option to subscribe to and unsubscribe from reply threads. (#4680, #4739)
|
||||||
|
- Minor: Added a message for when Chatterino joins a channel (#4616)
|
||||||
|
- Minor: Add accelerators to the right click menu for messages (#4705)
|
||||||
|
- Minor: Add pin action to usercards and reply threads. (#4692)
|
||||||
|
- Minor: 7TV badges now automatically update upon changing. (#4512)
|
||||||
|
- Minor: Stream status requests are now batched. (#4713)
|
||||||
|
- Minor: Added `/c2-theme-autoreload` command to automatically reload a custom theme. This is useful for when you're developing your own theme. (#4718)
|
||||||
|
- Minor: Remove restriction on Go To Message on system messages from search. (#4614)
|
||||||
|
- Minor: Highlights loaded from message history will now correctly appear in the /mentions tab. (#4475)
|
||||||
|
- Minor: All channels opened in browser tabs are synced when using the extension for quicker switching between tabs. (#4741)
|
||||||
|
- Minor: Show channel point redemptions without messages in usercard. (#4557)
|
||||||
|
- Minor: Allow for customizing the behavior of `Right Click`ing of usernames. (#4622, #4751)
|
||||||
|
- Minor: Added support for opening incognito links in firefox-esr and chromium. (#4745)
|
||||||
|
- Minor: Added support for opening incognito links under Linux/BSD using XDG. (#4745)
|
||||||
|
- Bugfix: Increased amount of blocked users loaded from 100 to 1,000. (#4721)
|
||||||
|
- Bugfix: Fixed generation of crashdumps by the browser-extension process when the browser was closed. (#4667)
|
||||||
|
- Bugfix: Fix spacing issue with mentions inside RTL text. (#4677)
|
||||||
|
- Bugfix: Fixed a crash when opening and closing a reply thread and switching the user. (#4675)
|
||||||
|
- Bugfix: Fixed a crash that could happen when closing splits before their display name was updated. This was especially noticeable after the live controller changes. (#4731)
|
||||||
|
- Bugfix: Fix visual glitches with smooth scrolling. (#4501)
|
||||||
|
- Bugfix: Fixed pings firing for the "Your username" highlight when not signed in. (#4698)
|
||||||
|
- Bugfix: Fixed partially broken filters on Qt 6 builds. (#4702)
|
||||||
|
- Bugfix: Fixed tooltips & popups sometimes showing up on the wrong monitor. (#4740)
|
||||||
|
- Bugfix: Fixed some network errors having `0` as their HTTP status. (#4704)
|
||||||
|
- Bugfix: Fixed crash that could occurr when closing the usercard too quickly after blocking or unblocking a user. (#4711)
|
||||||
|
- Bugfix: Fixed highlights sometimes not working after changing sound device, or switching users in your operating system. (#4729)
|
||||||
|
- Bugfix: Fixed key bindings not showing in context menus on Mac. (#4722)
|
||||||
|
- Bugfix: Fixed tab completion rarely completing the wrong word. (#4735)
|
||||||
|
- Bugfix: Fixed an issue where Subscriptions & Announcements that contained ignored phrases would still appear if the Block option was enabled. (#4748)
|
||||||
|
- Dev: Added command to set Qt's logging filter/rules at runtime (`/c2-set-logging-rules`). (#4637)
|
||||||
|
- Dev: Added the ability to see & load custom themes from the Themes directory. No stable promises are made of this feature, changes might be made that breaks custom themes without notice. (#4570)
|
||||||
|
- Dev: Added test cases for emote and tab completion. (#4644)
|
||||||
|
- Dev: Fixed `clang-tidy-review` action not picking up dependencies. (#4648)
|
||||||
|
- Dev: Expanded upon `$$$` test channels. (#4655)
|
||||||
|
- Dev: Added tools to help debug image GC. (#4578)
|
||||||
|
- Dev: Removed duplicate license when having plugins enabled. (#4665)
|
||||||
|
- Dev: Replace our QObjectRef class with Qt's QPointer class. (#4666)
|
||||||
|
- Dev: Fixed warnings about QWidgets already having a QLayout. (#4672)
|
||||||
|
- Dev: Fixed undefined behavior when loading non-existant credentials. (#4673)
|
||||||
|
- Dev: Added support for compiling with `sccache`. (#4678)
|
||||||
|
- Dev: Added `sccache` in Windows CI. (#4678)
|
||||||
|
- Dev: Moved preprocessor Git and date definitions to executables only. (#4681)
|
||||||
|
- Dev: Refactored tests to be able to use `ctest` and run in debug builds. (#4700)
|
||||||
|
- Dev: Added the ability to use an alternate linker using the `-DUSE_ALTERNATE_LINKER=...` CMake parameter. (#4711)
|
||||||
|
- Dev: The Windows installer is now built in CI. (#4408)
|
||||||
|
- Dev: Removed `getApp` and `getSettings` calls from message rendering. (#4535)
|
||||||
|
- Dev: Get the default browser executable instead of the entire command line when opening incognito links. (#4745)
|
||||||
|
|
||||||
|
## 2.4.4
|
||||||
|
|
||||||
- Minor: Added a Send button in the input box so you can click to send a message. This is disabled by default and can be enabled with the "Show send message button" setting. (#4607)
|
- Minor: Added a Send button in the input box so you can click to send a message. This is disabled by default and can be enabled with the "Show send message button" setting. (#4607)
|
||||||
- Minor: Improved error messages when the updater fails a download. (#4594)
|
- Minor: Improved error messages when the updater fails a download. (#4594)
|
||||||
- Minor: Added `/shield` and `/shieldoff` commands to toggle shield mode. (#4580)
|
- Minor: Added `/shield` and `/shieldoff` commands to toggle shield mode. (#4580)
|
||||||
|
@ -9,8 +68,10 @@
|
||||||
- Bugfix: Fixed the menu warping on macOS on Qt6. (#4595)
|
- Bugfix: Fixed the menu warping on macOS on Qt6. (#4595)
|
||||||
- Bugfix: Fixed link tooltips not showing unless the thumbnail setting was enabled. (#4597)
|
- Bugfix: Fixed link tooltips not showing unless the thumbnail setting was enabled. (#4597)
|
||||||
- Bugfix: Domains starting with `http` are now parsed as links again. (#4598)
|
- Bugfix: Domains starting with `http` are now parsed as links again. (#4598)
|
||||||
|
- Bugfix: Reduced the size of the update prompt to prevent it from going off the users screen. (#4626)
|
||||||
- Bugfix: Fixed click effects on buttons not being antialiased. (#4473)
|
- Bugfix: Fixed click effects on buttons not being antialiased. (#4473)
|
||||||
- Bugfix: Fixed Ctrl+Backspace not working after Select All in chat search popup. (#4461)
|
- Bugfix: Fixed Ctrl+Backspace not working after Select All in chat search popup. (#4461)
|
||||||
|
- Bugfix: Fixed crash when scrolling up really fast. (#4621)
|
||||||
- Dev: Added the ability to control the `followRedirect` mode for requests. (#4594)
|
- Dev: Added the ability to control the `followRedirect` mode for requests. (#4594)
|
||||||
|
|
||||||
## 2.4.3
|
## 2.4.3
|
||||||
|
|
|
@ -8,7 +8,7 @@ list(APPEND CMAKE_MODULE_PATH
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/sanitizers-cmake/cmake"
|
"${CMAKE_SOURCE_DIR}/cmake/sanitizers-cmake/cmake"
|
||||||
)
|
)
|
||||||
|
|
||||||
project(chatterino VERSION 2.4.3)
|
project(chatterino VERSION 2.4.4)
|
||||||
|
|
||||||
option(BUILD_APP "Build Chatterino" ON)
|
option(BUILD_APP "Build Chatterino" ON)
|
||||||
option(BUILD_TESTS "Build the tests for Chatterino" OFF)
|
option(BUILD_TESTS "Build the tests for Chatterino" OFF)
|
||||||
|
@ -42,11 +42,51 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(CCACHE_PROGRAM ccache)
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
if (CCACHE_PROGRAM)
|
find_program(SCCACHE_PROGRAM sccache)
|
||||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
if (SCCACHE_PROGRAM)
|
||||||
message("Using ${CCACHE_PROGRAM} for speeding up build")
|
set(_compiler_launcher ${SCCACHE_PROGRAM})
|
||||||
|
elseif (CCACHE_PROGRAM)
|
||||||
|
set(_compiler_launcher ${CCACHE_PROGRAM})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
# Alternate linker code taken from heavyai/heavydb
|
||||||
|
# https://github.com/heavyai/heavydb/blob/0517d99b467806f6af7b4c969e351368a667497d/CMakeLists.txt#L87-L103
|
||||||
|
macro(set_alternate_linker linker)
|
||||||
|
find_program(LINKER_EXECUTABLE ld.${USE_ALTERNATE_LINKER} ${USE_ALTERNATE_LINKER})
|
||||||
|
if(LINKER_EXECUTABLE)
|
||||||
|
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 12.0.0)
|
||||||
|
add_link_options("-ld-path=${USE_ALTERNATE_LINKER}")
|
||||||
|
else()
|
||||||
|
add_link_options("-fuse-ld=${USE_ALTERNATE_LINKER}")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(USE_ALTERNATE_LINKER "" CACHE STRING "Use alternate linker" FORCE)
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
set(USE_ALTERNATE_LINKER "" CACHE STRING "Use alternate linker. Leave empty for system default; alternatives are 'gold', 'lld', 'bfd', 'mold'")
|
||||||
|
if(NOT "${USE_ALTERNATE_LINKER}" STREQUAL "")
|
||||||
|
set_alternate_linker(${USE_ALTERNATE_LINKER})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (_compiler_launcher)
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${_compiler_launcher}" CACHE STRING "CXX compiler launcher")
|
||||||
|
message(STATUS "Using ${_compiler_launcher} for speeding up build")
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
# /Zi can't be used with (s)ccache
|
||||||
|
# Use /Z7 instead (debug info in object files)
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
|
||||||
|
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||||
|
string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/GIT.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/cmake/GIT.cmake)
|
||||||
|
|
||||||
find_package(Qt${MAJOR_QT_VERSION} REQUIRED
|
find_package(Qt${MAJOR_QT_VERSION} REQUIRED
|
||||||
|
@ -72,7 +112,7 @@ if (WIN32)
|
||||||
find_package(WinToast REQUIRED)
|
find_package(WinToast REQUIRED)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
find_package(Sanitizers)
|
find_package(Sanitizers QUIET)
|
||||||
|
|
||||||
# Find boost on the system
|
# Find boost on the system
|
||||||
# `OPTIONAL_COMPONENTS random` is required for vcpkg builds to link.
|
# `OPTIONAL_COMPONENTS random` is required for vcpkg builds to link.
|
||||||
|
@ -119,6 +159,7 @@ find_package(RapidJSON REQUIRED)
|
||||||
find_package(Websocketpp REQUIRED)
|
find_package(Websocketpp REQUIRED)
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
|
include(GoogleTest)
|
||||||
# For MSVC: Prevent overriding the parent project's compiler/linker settings
|
# For MSVC: Prevent overriding the parent project's compiler/linker settings
|
||||||
# See https://github.com/google/googletest/blob/main/googletest/README.md#visual-studio-dynamic-vs-static-runtimes
|
# See https://github.com/google/googletest/blob/main/googletest/README.md#visual-studio-dynamic-vs-static-runtimes
|
||||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
|
@ -166,6 +207,16 @@ if (BUILD_WITH_CRASHPAD)
|
||||||
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/crashpad" EXCLUDE_FROM_ALL)
|
add_subdirectory("${CMAKE_SOURCE_DIR}/lib/crashpad" EXCLUDE_FROM_ALL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Used to provide a date of build in the About page (for nightly builds). Getting the actual time of
|
||||||
|
# compilation in CMake is a more involved, as documented in https://stackoverflow.com/q/24292898.
|
||||||
|
# For CI runs, however, the date of build file generation should be consistent with the date of
|
||||||
|
# compilation so this approximation is "good enough" for our purpose.
|
||||||
|
if (DEFINED ENV{CHATTERINO_SKIP_DATE_GEN})
|
||||||
|
set(cmake_gen_date "1970-01-01")
|
||||||
|
else ()
|
||||||
|
string(TIMESTAMP cmake_gen_date "%Y-%m-%d")
|
||||||
|
endif ()
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
@ -174,6 +225,10 @@ include(cmake/resources/generate_resources.cmake)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
if (BUILD_TESTS OR BUILD_BENCHMARKS)
|
||||||
|
add_subdirectory(mocks)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
|
12
README.md
12
README.md
|
@ -44,6 +44,18 @@ git submodule update --init --recursive
|
||||||
|
|
||||||
[Building on FreeBSD](../master/BUILDING_ON_FREEBSD.md)
|
[Building on FreeBSD](../master/BUILDING_ON_FREEBSD.md)
|
||||||
|
|
||||||
|
## Git blame
|
||||||
|
|
||||||
|
This project has big commits in the history which for example update all line
|
||||||
|
endings. To improve the output of git-blame, consider setting:
|
||||||
|
|
||||||
|
```
|
||||||
|
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
```
|
||||||
|
|
||||||
|
This will ignore all revisions mentioned in the [`.git-blame-ignore-revs`
|
||||||
|
file](./.git-blame-ignore-revs). GitHub does this by default.
|
||||||
|
|
||||||
## Code style
|
## Code style
|
||||||
|
|
||||||
The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format.
|
The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format.
|
||||||
|
|
|
@ -15,6 +15,7 @@ add_executable(${PROJECT_NAME} ${benchmark_SOURCES})
|
||||||
add_sanitizers(${PROJECT_NAME})
|
add_sanitizers(${PROJECT_NAME})
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-lib)
|
target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-lib)
|
||||||
|
target_link_libraries(${PROJECT_NAME} PRIVATE chatterino-mocks)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark)
|
target_link_libraries(${PROJECT_NAME} PRIVATE benchmark::benchmark)
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/SharedMessageBuilder.hpp"
|
#include "messages/SharedMessageBuilder.hpp"
|
||||||
|
#include "mocks/EmptyApplication.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
|
|
||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
using namespace chatterino;
|
using namespace chatterino;
|
||||||
|
|
||||||
|
@ -45,65 +47,17 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class MockApplication : IApplication
|
class MockApplication : mock::EmptyApplication
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Theme *getThemes() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
Fonts *getFonts() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
IEmotes *getEmotes() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
AccountController *getAccounts() override
|
AccountController *getAccounts() override
|
||||||
{
|
{
|
||||||
return &this->accounts;
|
return &this->accounts;
|
||||||
}
|
}
|
||||||
HotkeyController *getHotkeys() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
WindowManager *getWindows() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
Toasts *getToasts() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
CommandController *getCommands() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
NotificationController *getNotifications() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
HighlightController *getHighlights() override
|
HighlightController *getHighlights() override
|
||||||
{
|
{
|
||||||
return &this->highlights;
|
return &this->highlights;
|
||||||
}
|
}
|
||||||
TwitchIrcServer *getTwitch() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
ChatterinoBadges *getChatterinoBadges() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
FfzBadges *getFfzBadges() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
IUserDataController *getUserData() override
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountController accounts;
|
AccountController accounts;
|
||||||
HighlightController highlights;
|
HighlightController highlights;
|
||||||
|
@ -113,7 +67,8 @@ public:
|
||||||
static void BM_HighlightTest(benchmark::State &state)
|
static void BM_HighlightTest(benchmark::State &state)
|
||||||
{
|
{
|
||||||
MockApplication mockApplication;
|
MockApplication mockApplication;
|
||||||
Settings settings("/tmp/c2-mock");
|
QTemporaryDir settingsDir;
|
||||||
|
Settings settings(settingsDir.path());
|
||||||
|
|
||||||
std::string message =
|
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))";
|
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))";
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
@ -8,11 +13,26 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
::benchmark::Initialize(&argc, argv);
|
::benchmark::Initialize(&argc, argv);
|
||||||
|
|
||||||
QtConcurrent::run([&app] {
|
// Ensure settings are initialized before any benchmarks are run
|
||||||
|
QTemporaryDir settingsDir;
|
||||||
|
settingsDir.setAutoRemove(false); // we'll remove it manually
|
||||||
|
chatterino::Settings settings(settingsDir.path());
|
||||||
|
|
||||||
|
QTimer::singleShot(0, [&]() {
|
||||||
::benchmark::RunSpecifiedBenchmarks();
|
::benchmark::RunSpecifiedBenchmarks();
|
||||||
|
|
||||||
app.exit(0);
|
settingsDir.remove();
|
||||||
|
|
||||||
|
// Pick up the last events from the eventloop
|
||||||
|
// Using a loop to catch events queueing other events (e.g. deletions)
|
||||||
|
for (size_t i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
QApplication::exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
return app.exec();
|
return QApplication::exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "SVG Color",
|
"title": "SVG Color",
|
||||||
"description": "This is stricter than Qt. You could theoretically put tabs an spaces between characters in a named color and capitalize the color.",
|
"description": "This enum is stricter than Qt. You could theoretically put tabs and spaces between characters in a named color and capitalize the color.",
|
||||||
"$comment": "https://www.w3.org/TR/SVG11/types.html#ColorKeywords",
|
"$comment": "https://www.w3.org/TR/SVG11/types.html#ColorKeywords",
|
||||||
"enum": [
|
"enum": [
|
||||||
"aliceblue",
|
"aliceblue",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
This can only be "whole versions", so if you're releasing `2.4.0-beta` you'll need to condense it to `2.4.0`
|
This can only be "whole versions", so if you're releasing `2.4.0-beta` you'll need to condense it to `2.4.0`
|
||||||
- [ ] Updated version code in `resources/com.chatterino.chatterino.appdata.xml`
|
- [ ] Updated version code in `resources/com.chatterino.chatterino.appdata.xml`
|
||||||
This cannot use dash to denote a pre-release identifier, you have to use a tilde instead.
|
This cannot use dash to denote a pre-release identifier, you have to use a tilde instead.
|
||||||
|
- [ ] Updated version code in `.CI/chatterino-installer.iss`
|
||||||
- [ ] Update the changelog `## Unreleased` section to the new version `CHANGELOG.md`
|
- [ ] Update the changelog `## Unreleased` section to the new version `CHANGELOG.md`
|
||||||
Make sure to leave the `## Unreleased` line unchanged for easier merges
|
Make sure to leave the `## Unreleased` line unchanged for easier merges
|
||||||
- [ ] Push directly to master :tf:
|
- [ ] Push directly to master :tf:
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit ec992578688b4c51c1856d08731cf7dcf10e446a
|
Subproject commit 432ff49ecccc1cdebf1a7646007bb0594ac3481f
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5d708c3f9cae12820e415d4f89c9eacbe2ab964b
|
Subproject commit ea39042e13645f63713425c05cc9ee4cfdcf0a40
|
55
mocks/.clang-format
Normal file
55
mocks/.clang-format
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
Language: Cpp
|
||||||
|
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
AlignEscapedNewlinesLeft: true
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLambdasOnASingleLine: Empty
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: false
|
||||||
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
|
BasedOnStyle: Google
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: "true"
|
||||||
|
AfterControlStatement: "true"
|
||||||
|
AfterFunction: "true"
|
||||||
|
AfterNamespace: "false"
|
||||||
|
BeforeCatch: "true"
|
||||||
|
BeforeElse: "true"
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakConstructorInitializersBeforeComma: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
DerivePointerBinding: false
|
||||||
|
FixNamespaceComments: true
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentWidth: 4
|
||||||
|
IndentWrappedFunctionNames: true
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
SortIncludes: CaseInsensitive
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
# Project includes
|
||||||
|
- Regex: '^"[a-zA-Z\._-]+(/[a-zA-Z0-9\._-]+)*"$'
|
||||||
|
Priority: 1
|
||||||
|
# Third party library includes
|
||||||
|
- Regex: '<[[:alnum:].]+/[a-zA-Z0-9\._\/-]+>'
|
||||||
|
Priority: 3
|
||||||
|
# Qt includes
|
||||||
|
- Regex: '^<Q[a-zA-Z0-9\._\/-]+>$'
|
||||||
|
Priority: 3
|
||||||
|
CaseSensitive: true
|
||||||
|
# LibCommuni includes
|
||||||
|
- Regex: "^<Irc[a-zA-Z]+>$"
|
||||||
|
Priority: 3
|
||||||
|
# Misc libraries
|
||||||
|
- Regex: '^<[a-zA-Z_0-9]+\.h(pp)?>$'
|
||||||
|
Priority: 3
|
||||||
|
# Standard library includes
|
||||||
|
- Regex: "^<[a-zA-Z_]+>$"
|
||||||
|
Priority: 4
|
||||||
|
NamespaceIndentation: Inner
|
||||||
|
PointerBindsToType: false
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
Standard: Auto
|
||||||
|
ReflowComments: false
|
7
mocks/CMakeLists.txt
Normal file
7
mocks/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
project(chatterino-mocks)
|
||||||
|
|
||||||
|
add_library(chatterino-mocks INTERFACE)
|
||||||
|
|
||||||
|
target_include_directories(chatterino-mocks INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME} INTERFACE gmock)
|
86
mocks/include/mocks/EmptyApplication.hpp
Normal file
86
mocks/include/mocks/EmptyApplication.hpp
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
|
|
||||||
|
namespace chatterino::mock {
|
||||||
|
|
||||||
|
class EmptyApplication : public IApplication
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Theme *getThemes() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fonts *getFonts() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
IEmotes *getEmotes() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountController *getAccounts() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HotkeyController *getHotkeys() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowManager *getWindows() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Toasts *getToasts() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandController *getCommands() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationController *getNotifications() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightController *getHighlights() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITwitchIrcServer *getTwitch() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatterinoBadges *getChatterinoBadges() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FfzBadges *getFfzBadges() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
IUserDataController *getUserData() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITwitchLiveController *getTwitchLiveController() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino::mock
|
414
mocks/include/mocks/Helix.hpp
Normal file
414
mocks/include/mocks/Helix.hpp
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
#include "util/CancellationToken.hpp"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace chatterino::mock {
|
||||||
|
|
||||||
|
class Helix : public IHelix
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Helix() = default;
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchUsers,
|
||||||
|
(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getUserByName,
|
||||||
|
(QString userName, ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
MOCK_METHOD(void, getUserById,
|
||||||
|
(QString userId, ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchUsersFollows,
|
||||||
|
(QString fromId, QString toId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getUserFollowers,
|
||||||
|
(QString userId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchStreams,
|
||||||
|
(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getStreamById,
|
||||||
|
(QString userId,
|
||||||
|
(ResultCallback<bool, HelixStream> successCallback),
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getStreamByName,
|
||||||
|
(QString userName,
|
||||||
|
(ResultCallback<bool, HelixStream> successCallback),
|
||||||
|
HelixFailureCallback failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchGames,
|
||||||
|
(QStringList gameIds, QStringList gameNames,
|
||||||
|
(ResultCallback<std::vector<HelixGame>> successCallback),
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, searchGames,
|
||||||
|
(QString gameName,
|
||||||
|
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getGameById,
|
||||||
|
(QString gameId, ResultCallback<HelixGame> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, createClip,
|
||||||
|
(QString channelId, ResultCallback<HelixClip> successCallback,
|
||||||
|
std::function<void(HelixClipError)> failureCallback,
|
||||||
|
std::function<void()> finallyCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, fetchChannels,
|
||||||
|
(QStringList userIDs,
|
||||||
|
ResultCallback<std::vector<HelixChannel>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getChannel,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<HelixChannel> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, createStreamMarker,
|
||||||
|
(QString broadcasterId, QString description,
|
||||||
|
ResultCallback<HelixStreamMarker> successCallback,
|
||||||
|
std::function<void(HelixStreamMarkerError)> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, loadBlocks,
|
||||||
|
(QString userId,
|
||||||
|
ResultCallback<std::vector<HelixBlock>> successCallback,
|
||||||
|
FailureCallback<QString> failureCallback,
|
||||||
|
CancellationToken &&token),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, blockUser,
|
||||||
|
(QString targetUserId, const QObject *caller,
|
||||||
|
std::function<void()> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, unblockUser,
|
||||||
|
(QString targetUserId, const QObject *caller,
|
||||||
|
std::function<void()> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, updateChannel,
|
||||||
|
(QString broadcasterId, QString gameId, QString language,
|
||||||
|
QString title,
|
||||||
|
std::function<void(NetworkResult)> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, manageAutoModMessages,
|
||||||
|
(QString userID, QString msgID, QString action,
|
||||||
|
std::function<void()> successCallback,
|
||||||
|
std::function<void(HelixAutoModMessageError)> failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getCheermotes,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixCheermoteSet>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getEmoteSetData,
|
||||||
|
(QString emoteSetId,
|
||||||
|
ResultCallback<HelixEmoteSetData> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, getChannelEmotes,
|
||||||
|
(QString broadcasterId,
|
||||||
|
ResultCallback<std::vector<HelixChannelEmote>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, getGlobalBadges,
|
||||||
|
(ResultCallback<HelixGlobalBadges> successCallback,
|
||||||
|
(FailureCallback<HelixGetGlobalBadgesError, QString> failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, getChannelBadges,
|
||||||
|
(QString broadcasterID,
|
||||||
|
ResultCallback<HelixChannelBadges> successCallback,
|
||||||
|
(FailureCallback<HelixGetChannelBadgesError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateUserChatColor,
|
||||||
|
(QString userID, QString color,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateUserChatColorError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, deleteChatMessages,
|
||||||
|
(QString broadcasterID, QString moderatorID, QString messageID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixDeleteChatMessagesError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, addChannelModerator,
|
||||||
|
(QString broadcasterID, QString userID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixAddChannelModeratorError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, removeChannelModerator,
|
||||||
|
(QString broadcasterID, QString userID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixRemoveChannelModeratorError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, sendChatAnnouncement,
|
||||||
|
(QString broadcasterID, QString moderatorID, QString message,
|
||||||
|
HelixAnnouncementColor color, ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixSendChatAnnouncementError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, addChannelVIP,
|
||||||
|
(QString broadcasterID, QString userID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixAddChannelVIPError, QString> failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, removeChannelVIP,
|
||||||
|
(QString broadcasterID, QString userID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixRemoveChannelVIPError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, unbanUser,
|
||||||
|
(QString broadcasterID, QString moderatorID, QString userID,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixUnbanUserError, QString> failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD( // /raid
|
||||||
|
void, startRaid,
|
||||||
|
(QString fromBroadcasterID, QString toBroadcasterId,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixStartRaidError, QString> failureCallback)),
|
||||||
|
(override)); // /raid
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD( // /unraid
|
||||||
|
void, cancelRaid,
|
||||||
|
(QString broadcasterID, ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixCancelRaidError, QString> failureCallback)),
|
||||||
|
(override)); // /unraid
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateEmoteMode,
|
||||||
|
(QString broadcasterID, QString moderatorID, bool emoteMode,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateFollowerMode,
|
||||||
|
(QString broadcasterID, QString moderatorID,
|
||||||
|
boost::optional<int> followerModeDuration,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateNonModeratorChatDelay,
|
||||||
|
(QString broadcasterID, QString moderatorID,
|
||||||
|
boost::optional<int> nonModeratorChatDelayDuration,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateSlowMode,
|
||||||
|
(QString broadcasterID, QString moderatorID,
|
||||||
|
boost::optional<int> slowModeWaitTime,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateSubscriberMode,
|
||||||
|
(QString broadcasterID, QString moderatorID,
|
||||||
|
bool subscriberMode,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateUniqueChatMode,
|
||||||
|
(QString broadcasterID, QString moderatorID,
|
||||||
|
bool uniqueChatMode,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
// update chat settings
|
||||||
|
|
||||||
|
// /timeout, /ban
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, banUser,
|
||||||
|
(QString broadcasterID, QString moderatorID, QString userID,
|
||||||
|
boost::optional<int> duration, QString reason,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixBanUserError, QString> failureCallback)),
|
||||||
|
(override)); // /timeout, /ban
|
||||||
|
|
||||||
|
// /w
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, sendWhisper,
|
||||||
|
(QString fromUserID, QString toUserID, QString message,
|
||||||
|
ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixWhisperError, QString> failureCallback)),
|
||||||
|
(override)); // /w
|
||||||
|
|
||||||
|
// getChatters
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, getChatters,
|
||||||
|
(QString broadcasterID, QString moderatorID, int maxChattersToFetch,
|
||||||
|
ResultCallback<HelixChatters> successCallback,
|
||||||
|
(FailureCallback<HelixGetChattersError, QString> failureCallback)),
|
||||||
|
(override)); // getChatters
|
||||||
|
|
||||||
|
// /vips
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, getChannelVIPs,
|
||||||
|
(QString broadcasterID,
|
||||||
|
ResultCallback<std::vector<HelixVip>> successCallback,
|
||||||
|
(FailureCallback<HelixListVIPsError, QString> failureCallback)),
|
||||||
|
(override)); // /vips
|
||||||
|
|
||||||
|
// /commercial
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, startCommercial,
|
||||||
|
(QString broadcasterID, int length,
|
||||||
|
ResultCallback<HelixStartCommercialResponse> successCallback,
|
||||||
|
(FailureCallback<HelixStartCommercialError, QString> failureCallback)),
|
||||||
|
(override)); // /commercial
|
||||||
|
|
||||||
|
// /mods
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, getModerators,
|
||||||
|
(QString broadcasterID, int maxModeratorsToFetch,
|
||||||
|
ResultCallback<std::vector<HelixModerator>> successCallback,
|
||||||
|
(FailureCallback<HelixGetModeratorsError, QString> failureCallback)),
|
||||||
|
(override)); // /mods
|
||||||
|
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateShieldMode,
|
||||||
|
(QString broadcasterID, QString moderatorID, bool isActive,
|
||||||
|
ResultCallback<HelixShieldModeStatus> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateShieldModeError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
// /shoutout
|
||||||
|
MOCK_METHOD(
|
||||||
|
void, sendShoutout,
|
||||||
|
(QString fromBroadcasterID, QString toBroadcasterID,
|
||||||
|
QString moderatorID, ResultCallback<> successCallback,
|
||||||
|
(FailureCallback<HelixSendShoutoutError, QString> failureCallback)),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
MOCK_METHOD(void, update, (QString clientId, QString oauthToken),
|
||||||
|
(override));
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// The extra parenthesis around the failure callback is because its type
|
||||||
|
// contains a comma
|
||||||
|
MOCK_METHOD(void, updateChatSettings,
|
||||||
|
(QString broadcasterID, QString moderatorID, QJsonObject json,
|
||||||
|
ResultCallback<HelixChatSettings> successCallback,
|
||||||
|
(FailureCallback<HelixUpdateChatSettingsError, QString>
|
||||||
|
failureCallback)),
|
||||||
|
(override));
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino::mock
|
BIN
resources/buttons/streamerModeEnabledDark.png
Normal file
BIN
resources/buttons/streamerModeEnabledDark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
13
resources/buttons/streamerModeEnabledDark.svg
Normal file
13
resources/buttons/streamerModeEnabledDark.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<!-- Created using Krita: https://krita.org -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:krita="http://krita.org/namespaces/svg/krita"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
width="69.12pt"
|
||||||
|
height="69.12pt"
|
||||||
|
viewBox="0 0 69.12 69.12">
|
||||||
|
<defs/>
|
||||||
|
<path id="shape0" transform="matrix(2.87999988555909 0 0 2.87999988555909 5.76071977108956 13.5080491863677)" fill="#ff9c9c" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M3.98975 0.238705C4.13015 0.379277 4.20909 0.57003 4.20909 0.768705C4.20909 0.96738 4.13015 1.15813 3.98975 1.2987C2.93474 2.35368 2.17587 3.66806 1.78971 5.1092C1.40355 6.55034 1.40355 8.06807 1.78971 9.50921C2.17587 10.9503 2.93474 12.2647 3.98975 13.3197C4.12625 13.4612 4.20181 13.6508 4.20001 13.8474C4.19821 14.0439 4.11919 14.2321 3.98012 14.3711C3.84105 14.51 3.65278 14.5888 3.4562 14.5905C3.25963 14.5921 3.07009 14.5163 2.92875 14.3797C-0.97625 10.4747 -0.97625 4.1437 2.92875 0.238705C3.06932 0.0983082 3.26008 0.0193649 3.45875 0.0193649C3.65742 0.0193649 3.84818 0.0983082 3.98875 0.238705ZM17.0707 0.238705C20.9757 4.1437 20.9757 10.4747 17.0707 14.3807C16.9286 14.5131 16.7405 14.5853 16.5462 14.5819C16.352 14.5785 16.1665 14.4997 16.0291 14.3623C15.8918 14.225 15.813 14.0395 15.8096 13.8452C15.8061 13.651 15.8783 13.4628 16.0107 13.3207C17.066 12.2657 17.825 10.9512 18.2112 9.50994C18.5975 8.06865 18.5975 6.55076 18.2112 5.10947C17.825 3.66819 17.066 2.3537 16.0107 1.2987C15.8922 1.18828 15.8126 1.04239 15.7839 0.882977C15.7552 0.723568 15.7789 0.559065 15.8514 0.414228C15.9239 0.269391 16.0414 0.151871 16.1863 0.0793556C16.3311 0.00684041 16.4956 -0.0168395 16.655 0.01188C16.8144 0.0405996 16.9603 0.120202 17.0707 0.238705ZM6.81775 3.0667C6.95815 3.20728 7.03709 3.39803 7.03709 3.5967C7.03709 3.79538 6.95815 3.98613 6.81775 4.1267C6.25903 4.68524 5.85712 5.38119 5.65261 6.14428C5.44809 6.90737 5.44809 7.71104 5.65261 8.47413C5.85712 9.23722 6.25903 9.93317 6.81775 10.4917C6.95018 10.6338 7.02235 10.822 7.01893 11.0162C7.0155 11.2105 6.93674 11.396 6.79937 11.5333C6.66201 11.6707 6.47651 11.7495 6.28227 11.7529C6.08804 11.7563 5.89988 11.6841 5.75775 11.5517C4.63305 10.4269 4.00053 8.89982 4.00053 7.3092C4.00053 5.71859 4.63305 4.19148 5.75775 3.0667C5.89832 2.92631 6.08908 2.84736 6.28775 2.84736C6.48642 2.84736 6.67718 2.92631 6.81775 3.0667ZM14.2427 3.0667C15.3675 4.19148 16 5.71859 16 7.3092C16 8.89982 15.3675 10.4269 14.2427 11.5517C14.1316 11.6669 13.9863 11.7436 13.8284 11.7704C13.6705 11.7971 13.5082 11.7727 13.3652 11.7005C13.2222 11.6284 13.106 11.5123 13.0338 11.3694C12.9615 11.2265 12.9369 11.0641 12.9635 10.9062C12.9901 10.7483 13.0666 10.603 13.1817 10.4917C13.7403 9.93319 14.142 9.23735 14.3465 8.4744C14.5509 7.71145 14.5509 6.90796 14.3465 6.14501C14.142 5.38206 13.7403 4.68622 13.1818 4.1277C13.0493 3.98558 12.9771 3.79742 12.9806 3.60318C12.984 3.40895 13.0628 3.22345 13.2001 3.08608C13.3375 2.94872 13.523 2.86996 13.7172 2.86653C13.9115 2.8631 14.0996 2.93527 14.2417 3.0677ZM9.99975 5.8097C10.3974 5.8097 10.7792 5.96785 11.0604 6.24904C11.3416 6.53024 11.4997 6.91203 11.4997 7.3097C11.4997 7.70738 11.3416 8.08917 11.0604 8.37037C10.7792 8.65156 10.3974 8.8097 9.99975 8.8097C9.60208 8.8097 9.22029 8.65156 8.93909 8.37037C8.65789 8.08917 8.49975 7.70738 8.49975 7.3097C8.49975 6.91203 8.65789 6.53024 8.93909 6.24904C9.22029 5.96785 9.60208 5.8097 9.99975 5.8097Z" sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
BIN
resources/buttons/streamerModeEnabledLight.png
Normal file
BIN
resources/buttons/streamerModeEnabledLight.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
13
resources/buttons/streamerModeEnabledLight.svg
Normal file
13
resources/buttons/streamerModeEnabledLight.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<!-- Created using Krita: https://krita.org -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:krita="http://krita.org/namespaces/svg/krita"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
width="69.12pt"
|
||||||
|
height="69.12pt"
|
||||||
|
viewBox="0 0 69.12 69.12">
|
||||||
|
<defs/>
|
||||||
|
<path id="shape0" transform="matrix(2.87999977111818 0 0 2.87999977111818 5.76071954217913 13.508048613566)" fill="#c10000" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" d="M3.98975 0.238705C4.13015 0.379277 4.20909 0.57003 4.20909 0.768705C4.20909 0.96738 4.13015 1.15813 3.98975 1.2987C2.93474 2.35368 2.17587 3.66806 1.78971 5.1092C1.40355 6.55034 1.40355 8.06807 1.78971 9.50921C2.17587 10.9503 2.93474 12.2647 3.98975 13.3197C4.12625 13.4612 4.20181 13.6508 4.20001 13.8474C4.19821 14.0439 4.11919 14.2321 3.98012 14.3711C3.84105 14.51 3.65278 14.5888 3.4562 14.5905C3.25963 14.5921 3.07009 14.5163 2.92875 14.3797C-0.97625 10.4747 -0.97625 4.1437 2.92875 0.238705C3.06932 0.0983082 3.26008 0.0193649 3.45875 0.0193649C3.65742 0.0193649 3.84818 0.0983082 3.98875 0.238705ZM17.0707 0.238705C20.9757 4.1437 20.9757 10.4747 17.0707 14.3807C16.9286 14.5131 16.7405 14.5853 16.5462 14.5819C16.352 14.5785 16.1665 14.4997 16.0291 14.3623C15.8918 14.225 15.813 14.0395 15.8096 13.8452C15.8061 13.651 15.8783 13.4628 16.0107 13.3207C17.066 12.2657 17.825 10.9512 18.2112 9.50994C18.5975 8.06865 18.5975 6.55076 18.2112 5.10947C17.825 3.66819 17.066 2.3537 16.0107 1.2987C15.8922 1.18828 15.8126 1.04239 15.7839 0.882977C15.7552 0.723568 15.7789 0.559065 15.8514 0.414228C15.9239 0.269391 16.0414 0.151871 16.1863 0.0793556C16.3311 0.00684042 16.4956 -0.0168395 16.655 0.01188C16.8144 0.0405996 16.9603 0.120202 17.0707 0.238705ZM6.81775 3.0667C6.95815 3.20728 7.03709 3.39803 7.03709 3.5967C7.03709 3.79538 6.95815 3.98613 6.81775 4.1267C6.25903 4.68524 5.85712 5.38119 5.65261 6.14428C5.44809 6.90737 5.44809 7.71104 5.65261 8.47413C5.85712 9.23722 6.25903 9.93317 6.81775 10.4917C6.95018 10.6338 7.02235 10.822 7.01893 11.0162C7.0155 11.2105 6.93674 11.396 6.79937 11.5333C6.66201 11.6707 6.47651 11.7495 6.28227 11.7529C6.08804 11.7563 5.89988 11.6841 5.75775 11.5517C4.63305 10.4269 4.00053 8.89982 4.00053 7.3092C4.00053 5.71859 4.63305 4.19148 5.75775 3.0667C5.89832 2.92631 6.08908 2.84736 6.28775 2.84736C6.48642 2.84736 6.67718 2.92631 6.81775 3.0667ZM14.2427 3.0667C15.3675 4.19148 16 5.71859 16 7.3092C16 8.89982 15.3675 10.4269 14.2427 11.5517C14.1316 11.6669 13.9863 11.7436 13.8284 11.7704C13.6705 11.7971 13.5082 11.7727 13.3652 11.7005C13.2222 11.6284 13.106 11.5123 13.0338 11.3694C12.9615 11.2265 12.9369 11.0641 12.9635 10.9062C12.9901 10.7483 13.0666 10.603 13.1817 10.4917C13.7403 9.93319 14.142 9.23735 14.3465 8.4744C14.5509 7.71145 14.5509 6.90796 14.3465 6.14501C14.142 5.38206 13.7403 4.68622 13.1818 4.1277C13.0493 3.98558 12.9771 3.79742 12.9806 3.60318C12.984 3.40895 13.0628 3.22345 13.2001 3.08608C13.3375 2.94872 13.523 2.86996 13.7172 2.86653C13.9115 2.8631 14.0996 2.93527 14.2417 3.0677ZM9.99975 5.8097C10.3974 5.8097 10.7792 5.96785 11.0604 6.24904C11.3416 6.53024 11.4997 6.91203 11.4997 7.3097C11.4997 7.70738 11.3416 8.08917 11.0604 8.37037C10.7792 8.65156 10.3974 8.8097 9.99975 8.8097C9.60208 8.8097 9.22029 8.65156 8.93909 8.37037C8.65789 8.08917 8.49975 7.70738 8.49975 7.3097C8.49975 6.91203 8.65789 6.53024 8.93909 6.24904C9.22029 5.96785 9.60208 5.8097 9.99975 5.8097Z" sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -32,6 +32,9 @@
|
||||||
<binary>chatterino</binary>
|
<binary>chatterino</binary>
|
||||||
</provides>
|
</provides>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="2.4.4" date="2023-05-14">
|
||||||
|
<url>https://github.com/Chatterino/chatterino2/releases/tag/v2.4.4</url>
|
||||||
|
</release>
|
||||||
<release version="2.4.3" date="2023-04-30">
|
<release version="2.4.3" date="2023-04-30">
|
||||||
<url>https://github.com/Chatterino/chatterino2/releases/tag/v2.4.3</url>
|
<url>https://github.com/Chatterino/chatterino2/releases/tag/v2.4.3</url>
|
||||||
</release>
|
</release>
|
||||||
|
|
|
@ -62,6 +62,9 @@ ScrubN | https://github.com/ScrubN | | Contributor
|
||||||
Cyclone | https://github.com/PsycloneTM | :/avatars/cyclone.png | Contributor
|
Cyclone | https://github.com/PsycloneTM | :/avatars/cyclone.png | Contributor
|
||||||
2547techno | https://github.com/2547techno | :/avatars/techno.png | Contributor
|
2547techno | https://github.com/2547techno | :/avatars/techno.png | Contributor
|
||||||
ZonianMidian | https://github.com/ZonianMidian | :/avatars/zonianmidian.png | Contributor
|
ZonianMidian | https://github.com/ZonianMidian | :/avatars/zonianmidian.png | Contributor
|
||||||
|
olafyang | https://github.com/olafyang | | Contributor
|
||||||
|
chrrs | https://github.com/chrrs | | Contributor
|
||||||
|
4rneee | https://github.com/4rneee | | Contributor
|
||||||
|
|
||||||
# If you are a contributor add yourself above this line
|
# If you are a contributor add yourself above this line
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# include "controllers/plugins/PluginController.hpp"
|
# include "controllers/plugins/PluginController.hpp"
|
||||||
#endif
|
#endif
|
||||||
#include "controllers/sound/SoundController.hpp"
|
#include "controllers/sound/SoundController.hpp"
|
||||||
|
#include "controllers/twitch/LiveController.hpp"
|
||||||
#include "controllers/userdata/UserDataController.hpp"
|
#include "controllers/userdata/UserDataController.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
@ -88,6 +89,7 @@ Application::Application(Settings &_settings, Paths &_paths)
|
||||||
, seventvBadges(&this->emplace<SeventvBadges>())
|
, seventvBadges(&this->emplace<SeventvBadges>())
|
||||||
, userData(&this->emplace<UserDataController>())
|
, userData(&this->emplace<UserDataController>())
|
||||||
, sound(&this->emplace<SoundController>())
|
, sound(&this->emplace<SoundController>())
|
||||||
|
, twitchLiveController(&this->emplace<TwitchLiveController>())
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
, plugins(&this->emplace<PluginController>())
|
, plugins(&this->emplace<PluginController>())
|
||||||
#endif
|
#endif
|
||||||
|
@ -245,6 +247,16 @@ IUserDataController *Application::getUserData()
|
||||||
return this->userData;
|
return this->userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ITwitchLiveController *Application::getTwitchLiveController()
|
||||||
|
{
|
||||||
|
return this->twitchLiveController;
|
||||||
|
}
|
||||||
|
|
||||||
|
ITwitchIrcServer *Application::getTwitch()
|
||||||
|
{
|
||||||
|
return this->twitch;
|
||||||
|
}
|
||||||
|
|
||||||
void Application::save()
|
void Application::save()
|
||||||
{
|
{
|
||||||
for (auto &singleton : this->singletons_)
|
for (auto &singleton : this->singletons_)
|
||||||
|
@ -258,7 +270,7 @@ void Application::initNm(Paths &paths)
|
||||||
(void)paths;
|
(void)paths;
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
# if defined QT_NO_DEBUG || defined C_DEBUG_NM
|
# if defined QT_NO_DEBUG || defined CHATTERINO_DEBUG_NM
|
||||||
registerNmHost(paths);
|
registerNmHost(paths);
|
||||||
this->nmServer.start();
|
this->nmServer.start();
|
||||||
# endif
|
# endif
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
|
|
||||||
|
#include <pajlada/signals.hpp>
|
||||||
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -10,6 +12,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchIrcServer;
|
class TwitchIrcServer;
|
||||||
|
class ITwitchIrcServer;
|
||||||
class PubSub;
|
class PubSub;
|
||||||
|
|
||||||
class CommandController;
|
class CommandController;
|
||||||
|
@ -20,6 +23,8 @@ class HotkeyController;
|
||||||
class IUserDataController;
|
class IUserDataController;
|
||||||
class UserDataController;
|
class UserDataController;
|
||||||
class SoundController;
|
class SoundController;
|
||||||
|
class ITwitchLiveController;
|
||||||
|
class TwitchLiveController;
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
class PluginController;
|
class PluginController;
|
||||||
#endif
|
#endif
|
||||||
|
@ -55,10 +60,11 @@ public:
|
||||||
virtual CommandController *getCommands() = 0;
|
virtual CommandController *getCommands() = 0;
|
||||||
virtual HighlightController *getHighlights() = 0;
|
virtual HighlightController *getHighlights() = 0;
|
||||||
virtual NotificationController *getNotifications() = 0;
|
virtual NotificationController *getNotifications() = 0;
|
||||||
virtual TwitchIrcServer *getTwitch() = 0;
|
virtual ITwitchIrcServer *getTwitch() = 0;
|
||||||
virtual ChatterinoBadges *getChatterinoBadges() = 0;
|
virtual ChatterinoBadges *getChatterinoBadges() = 0;
|
||||||
virtual FfzBadges *getFfzBadges() = 0;
|
virtual FfzBadges *getFfzBadges() = 0;
|
||||||
virtual IUserDataController *getUserData() = 0;
|
virtual IUserDataController *getUserData() = 0;
|
||||||
|
virtual ITwitchLiveController *getTwitchLiveController() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Application : public IApplication
|
class Application : public IApplication
|
||||||
|
@ -98,6 +104,10 @@ public:
|
||||||
UserDataController *const userData{};
|
UserDataController *const userData{};
|
||||||
SoundController *const sound{};
|
SoundController *const sound{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
TwitchLiveController *const twitchLiveController{};
|
||||||
|
|
||||||
|
public:
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
PluginController *const plugins{};
|
PluginController *const plugins{};
|
||||||
#endif
|
#endif
|
||||||
|
@ -141,10 +151,7 @@ public:
|
||||||
{
|
{
|
||||||
return this->highlights;
|
return this->highlights;
|
||||||
}
|
}
|
||||||
TwitchIrcServer *getTwitch() override
|
ITwitchIrcServer *getTwitch() override;
|
||||||
{
|
|
||||||
return this->twitch;
|
|
||||||
}
|
|
||||||
ChatterinoBadges *getChatterinoBadges() override
|
ChatterinoBadges *getChatterinoBadges() override
|
||||||
{
|
{
|
||||||
return this->chatterinoBadges;
|
return this->chatterinoBadges;
|
||||||
|
@ -154,6 +161,9 @@ public:
|
||||||
return this->ffzBadges;
|
return this->ffzBadges;
|
||||||
}
|
}
|
||||||
IUserDataController *getUserData() override;
|
IUserDataController *getUserData() override;
|
||||||
|
ITwitchLiveController *getTwitchLiveController() override;
|
||||||
|
|
||||||
|
pajlada::Signals::NoArgSignal streamerModeChanged;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addSingleton(Singleton *singleton);
|
void addSingleton(Singleton *singleton);
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void runLoop(NativeMessagingClient &client)
|
void runLoop()
|
||||||
{
|
{
|
||||||
auto received_message = std::make_shared<std::atomic_bool>(true);
|
auto received_message = std::make_shared<std::atomic_bool>(true);
|
||||||
|
|
||||||
|
@ -73,8 +73,9 @@ namespace {
|
||||||
|
|
||||||
received_message->store(true);
|
received_message->store(true);
|
||||||
|
|
||||||
client.sendMessage(data);
|
nm::client::sendMessage(data);
|
||||||
}
|
}
|
||||||
|
_Exit(0);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -82,9 +83,7 @@ void runBrowserExtensionHost()
|
||||||
{
|
{
|
||||||
initFileMode();
|
initFileMode();
|
||||||
|
|
||||||
NativeMessagingClient client;
|
runLoop();
|
||||||
|
|
||||||
runLoop(client);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
set(LIBRARY_PROJECT "${PROJECT_NAME}-lib")
|
set(LIBRARY_PROJECT "${PROJECT_NAME}-lib")
|
||||||
|
set(VERSION_PROJECT "${LIBRARY_PROJECT}-version")
|
||||||
set(EXECUTABLE_PROJECT "${PROJECT_NAME}")
|
set(EXECUTABLE_PROJECT "${PROJECT_NAME}")
|
||||||
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050F00)
|
add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050F00)
|
||||||
|
|
||||||
|
# registers the native messageing host
|
||||||
|
option(CHATTERINO_DEBUG_NATIVE_MESSAGES "Debug native messages" OFF)
|
||||||
|
|
||||||
set(SOURCE_FILES
|
set(SOURCE_FILES
|
||||||
Application.cpp
|
Application.cpp
|
||||||
Application.hpp
|
Application.hpp
|
||||||
|
@ -32,6 +36,7 @@ set(SOURCE_FILES
|
||||||
common/Env.hpp
|
common/Env.hpp
|
||||||
common/LinkParser.cpp
|
common/LinkParser.cpp
|
||||||
common/LinkParser.hpp
|
common/LinkParser.hpp
|
||||||
|
common/Literals.hpp
|
||||||
common/Modes.cpp
|
common/Modes.cpp
|
||||||
common/Modes.hpp
|
common/Modes.hpp
|
||||||
common/NetworkCommon.cpp
|
common/NetworkCommon.cpp
|
||||||
|
@ -46,8 +51,6 @@ set(SOURCE_FILES
|
||||||
common/NetworkResult.hpp
|
common/NetworkResult.hpp
|
||||||
common/QLogging.cpp
|
common/QLogging.cpp
|
||||||
common/QLogging.hpp
|
common/QLogging.hpp
|
||||||
common/Version.cpp
|
|
||||||
common/Version.hpp
|
|
||||||
common/WindowDescriptors.cpp
|
common/WindowDescriptors.cpp
|
||||||
common/WindowDescriptors.hpp
|
common/WindowDescriptors.hpp
|
||||||
|
|
||||||
|
@ -60,10 +63,14 @@ set(SOURCE_FILES
|
||||||
controllers/accounts/AccountModel.cpp
|
controllers/accounts/AccountModel.cpp
|
||||||
controllers/accounts/AccountModel.hpp
|
controllers/accounts/AccountModel.hpp
|
||||||
|
|
||||||
|
controllers/commands/builtin/chatterino/Debugging.cpp
|
||||||
|
controllers/commands/builtin/chatterino/Debugging.hpp
|
||||||
controllers/commands/builtin/twitch/ChatSettings.cpp
|
controllers/commands/builtin/twitch/ChatSettings.cpp
|
||||||
controllers/commands/builtin/twitch/ChatSettings.hpp
|
controllers/commands/builtin/twitch/ChatSettings.hpp
|
||||||
controllers/commands/builtin/twitch/ShieldMode.cpp
|
controllers/commands/builtin/twitch/ShieldMode.cpp
|
||||||
controllers/commands/builtin/twitch/ShieldMode.hpp
|
controllers/commands/builtin/twitch/ShieldMode.hpp
|
||||||
|
controllers/commands/builtin/twitch/Shoutout.cpp
|
||||||
|
controllers/commands/builtin/twitch/Shoutout.hpp
|
||||||
controllers/commands/CommandContext.hpp
|
controllers/commands/CommandContext.hpp
|
||||||
controllers/commands/CommandController.cpp
|
controllers/commands/CommandController.cpp
|
||||||
controllers/commands/CommandController.hpp
|
controllers/commands/CommandController.hpp
|
||||||
|
@ -163,13 +170,16 @@ set(SOURCE_FILES
|
||||||
controllers/plugins/LuaUtilities.cpp
|
controllers/plugins/LuaUtilities.cpp
|
||||||
controllers/plugins/LuaUtilities.hpp
|
controllers/plugins/LuaUtilities.hpp
|
||||||
|
|
||||||
|
controllers/sound/SoundController.cpp
|
||||||
|
controllers/sound/SoundController.hpp
|
||||||
|
|
||||||
|
controllers/twitch/LiveController.cpp
|
||||||
|
controllers/twitch/LiveController.hpp
|
||||||
|
|
||||||
controllers/userdata/UserDataController.cpp
|
controllers/userdata/UserDataController.cpp
|
||||||
controllers/userdata/UserDataController.hpp
|
controllers/userdata/UserDataController.hpp
|
||||||
controllers/userdata/UserData.hpp
|
controllers/userdata/UserData.hpp
|
||||||
|
|
||||||
controllers/sound/SoundController.cpp
|
|
||||||
controllers/sound/SoundController.hpp
|
|
||||||
|
|
||||||
debug/Benchmark.cpp
|
debug/Benchmark.cpp
|
||||||
debug/Benchmark.hpp
|
debug/Benchmark.hpp
|
||||||
|
|
||||||
|
@ -199,6 +209,8 @@ set(SOURCE_FILES
|
||||||
messages/layouts/MessageLayout.hpp
|
messages/layouts/MessageLayout.hpp
|
||||||
messages/layouts/MessageLayoutContainer.cpp
|
messages/layouts/MessageLayoutContainer.cpp
|
||||||
messages/layouts/MessageLayoutContainer.hpp
|
messages/layouts/MessageLayoutContainer.hpp
|
||||||
|
messages/layouts/MessageLayoutContext.cpp
|
||||||
|
messages/layouts/MessageLayoutContext.hpp
|
||||||
messages/layouts/MessageLayoutElement.cpp
|
messages/layouts/MessageLayoutElement.cpp
|
||||||
messages/layouts/MessageLayoutElement.hpp
|
messages/layouts/MessageLayoutElement.hpp
|
||||||
messages/search/AuthorPredicate.cpp
|
messages/search/AuthorPredicate.cpp
|
||||||
|
@ -276,8 +288,11 @@ set(SOURCE_FILES
|
||||||
providers/liveupdates/BasicPubSubManager.hpp
|
providers/liveupdates/BasicPubSubManager.hpp
|
||||||
providers/liveupdates/BasicPubSubWebsocket.hpp
|
providers/liveupdates/BasicPubSubWebsocket.hpp
|
||||||
|
|
||||||
|
providers/seventv/SeventvAPI.cpp
|
||||||
|
providers/seventv/SeventvAPI.hpp
|
||||||
providers/seventv/SeventvBadges.cpp
|
providers/seventv/SeventvBadges.cpp
|
||||||
providers/seventv/SeventvBadges.hpp
|
providers/seventv/SeventvBadges.hpp
|
||||||
|
providers/seventv/SeventvCosmetics.hpp
|
||||||
providers/seventv/SeventvEmotes.cpp
|
providers/seventv/SeventvEmotes.cpp
|
||||||
providers/seventv/SeventvEmotes.hpp
|
providers/seventv/SeventvEmotes.hpp
|
||||||
providers/seventv/SeventvEventAPI.cpp
|
providers/seventv/SeventvEventAPI.cpp
|
||||||
|
@ -378,6 +393,7 @@ set(SOURCE_FILES
|
||||||
|
|
||||||
util/AttachToConsole.cpp
|
util/AttachToConsole.cpp
|
||||||
util/AttachToConsole.hpp
|
util/AttachToConsole.hpp
|
||||||
|
util/CancellationToken.hpp
|
||||||
util/Clipboard.cpp
|
util/Clipboard.cpp
|
||||||
util/Clipboard.hpp
|
util/Clipboard.hpp
|
||||||
util/ConcurrentMap.hpp
|
util/ConcurrentMap.hpp
|
||||||
|
@ -397,6 +413,8 @@ set(SOURCE_FILES
|
||||||
util/IncognitoBrowser.hpp
|
util/IncognitoBrowser.hpp
|
||||||
util/InitUpdateButton.cpp
|
util/InitUpdateButton.cpp
|
||||||
util/InitUpdateButton.hpp
|
util/InitUpdateButton.hpp
|
||||||
|
util/IpcQueue.cpp
|
||||||
|
util/IpcQueue.hpp
|
||||||
util/LayoutHelper.cpp
|
util/LayoutHelper.cpp
|
||||||
util/LayoutHelper.hpp
|
util/LayoutHelper.hpp
|
||||||
util/NuulsUploader.cpp
|
util/NuulsUploader.cpp
|
||||||
|
@ -420,6 +438,12 @@ set(SOURCE_FILES
|
||||||
util/TypeName.hpp
|
util/TypeName.hpp
|
||||||
util/WindowsHelper.cpp
|
util/WindowsHelper.cpp
|
||||||
util/WindowsHelper.hpp
|
util/WindowsHelper.hpp
|
||||||
|
util/XDGDesktopFile.cpp
|
||||||
|
util/XDGDesktopFile.hpp
|
||||||
|
util/XDGDirectory.cpp
|
||||||
|
util/XDGDirectory.hpp
|
||||||
|
util/XDGHelper.cpp
|
||||||
|
util/XDGHelper.hpp
|
||||||
|
|
||||||
util/serialize/Container.hpp
|
util/serialize/Container.hpp
|
||||||
|
|
||||||
|
@ -706,7 +730,12 @@ if (BUILD_APP)
|
||||||
else()
|
else()
|
||||||
add_executable(${EXECUTABLE_PROJECT} main.cpp)
|
add_executable(${EXECUTABLE_PROJECT} main.cpp)
|
||||||
endif()
|
endif()
|
||||||
add_sanitizers(${EXECUTABLE_PROJECT})
|
|
||||||
|
if(COMMAND add_sanitizers)
|
||||||
|
add_sanitizers(${EXECUTABLE_PROJECT})
|
||||||
|
else()
|
||||||
|
message(WARNING "Sanitizers support is disabled")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(${EXECUTABLE_PROJECT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/autogen/)
|
target_include_directories(${EXECUTABLE_PROJECT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/autogen/)
|
||||||
|
|
||||||
|
@ -790,15 +819,26 @@ set_target_properties(${LIBRARY_PROJECT}
|
||||||
AUTOUIC ON
|
AUTOUIC ON
|
||||||
)
|
)
|
||||||
|
|
||||||
# Used to provide a date of build in the About page (for nightly builds). Getting the actual time of
|
# The version project has definitions about the build.
|
||||||
# compilation in CMake is a more involved, as documented in https://stackoverflow.com/q/24292898.
|
# To avoid recompilations because of changing preprocessor definitions,
|
||||||
# For CI runs, however, the date of build file generation should be consistent with the date of
|
# this is its own project.
|
||||||
# compilation so this approximation is "good enough" for our purpose.
|
set(VERSION_SOURCE_FILES common/Version.cpp common/Version.hpp)
|
||||||
if (DEFINED ENV{CHATTERINO_SKIP_DATE_GEN})
|
add_library(${VERSION_PROJECT} STATIC ${VERSION_SOURCE_FILES})
|
||||||
set(cmake_gen_date "1970-01-01")
|
|
||||||
else ()
|
# source group for IDEs
|
||||||
string(TIMESTAMP cmake_gen_date "%Y-%m-%d")
|
source_group(TREE ${CMAKE_SOURCE_DIR} FILES ${VERSION_SOURCE_FILES})
|
||||||
endif ()
|
target_include_directories(${VERSION_PROJECT} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(${VERSION_PROJECT} PRIVATE Qt${MAJOR_QT_VERSION}::Core)
|
||||||
|
target_compile_definitions(${VERSION_PROJECT} PRIVATE
|
||||||
|
CHATTERINO_GIT_HASH=\"${GIT_HASH}\"
|
||||||
|
CHATTERINO_GIT_RELEASE=\"${GIT_RELEASE}\"
|
||||||
|
CHATTERINO_GIT_COMMIT=\"${GIT_COMMIT}\"
|
||||||
|
CHATTERINO_GIT_MODIFIED=${GIT_MODIFIED}
|
||||||
|
|
||||||
|
CHATTERINO_CMAKE_GEN_DATE=\"${cmake_gen_date}\"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_PROJECT} PRIVATE ${VERSION_PROJECT})
|
||||||
|
|
||||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||||
CHATTERINO
|
CHATTERINO
|
||||||
|
@ -806,18 +846,7 @@ target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||||
AB_CUSTOM_SETTINGS
|
AB_CUSTOM_SETTINGS
|
||||||
IRC_STATIC
|
IRC_STATIC
|
||||||
IRC_NAMESPACE=Communi
|
IRC_NAMESPACE=Communi
|
||||||
|
|
||||||
CHATTERINO_GIT_HASH=\"${GIT_HASH}\"
|
|
||||||
CHATTERINO_GIT_RELEASE=\"${GIT_RELEASE}\"
|
|
||||||
CHATTERINO_GIT_COMMIT=\"${GIT_COMMIT}\"
|
|
||||||
|
|
||||||
CHATTERINO_CMAKE_GEN_DATE=\"${cmake_gen_date}\"
|
|
||||||
)
|
)
|
||||||
if (GIT_MODIFIED)
|
|
||||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
|
||||||
CHATTERINO_GIT_MODIFIED
|
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
if (USE_SYSTEM_QTKEYCHAIN)
|
if (USE_SYSTEM_QTKEYCHAIN)
|
||||||
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
target_compile_definitions(${LIBRARY_PROJECT} PUBLIC
|
||||||
CMAKE_BUILD
|
CMAKE_BUILD
|
||||||
|
@ -831,6 +860,9 @@ if (WIN32)
|
||||||
set_target_properties(${EXECUTABLE_PROJECT} PROPERTIES WIN32_EXECUTABLE TRUE)
|
set_target_properties(${EXECUTABLE_PROJECT} PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
|
if (CHATTERINO_DEBUG_NATIVE_MESSAGES)
|
||||||
|
target_compile_definitions(${LIBRARY_PROJECT} PRIVATE CHATTERINO_DEBUG_NM)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
target_compile_options(${LIBRARY_PROJECT} PUBLIC /EHsc /bigobj)
|
target_compile_options(${LIBRARY_PROJECT} PUBLIC /EHsc /bigobj)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
# include <boost/circular_buffer.hpp>
|
||||||
# include <boost/current_function.hpp>
|
# include <boost/current_function.hpp>
|
||||||
# include <boost/foreach.hpp>
|
# include <boost/foreach.hpp>
|
||||||
# include <boost/noncopyable.hpp>
|
# include <boost/noncopyable.hpp>
|
||||||
# include <boost/optional.hpp>
|
# include <boost/optional.hpp>
|
||||||
|
# include <boost/signals2.hpp>
|
||||||
# include <IrcCommand>
|
# include <IrcCommand>
|
||||||
# include <IrcConnection>
|
# include <IrcConnection>
|
||||||
# include <IrcMessage>
|
# include <IrcMessage>
|
||||||
|
@ -12,40 +14,29 @@
|
||||||
# include <pajlada/signals/connection.hpp>
|
# include <pajlada/signals/connection.hpp>
|
||||||
# include <pajlada/signals/signal.hpp>
|
# include <pajlada/signals/signal.hpp>
|
||||||
# include <QAbstractListModel>
|
# include <QAbstractListModel>
|
||||||
# include <QAbstractNativeEventFilter>
|
|
||||||
# include <QAction>
|
# include <QAction>
|
||||||
# include <QApplication>
|
# include <QApplication>
|
||||||
# include <QBrush>
|
# include <QBrush>
|
||||||
# include <QBuffer>
|
# include <QBuffer>
|
||||||
# include <QButtonGroup>
|
|
||||||
# include <QByteArray>
|
# include <QByteArray>
|
||||||
# include <QCheckBox>
|
# include <QCheckBox>
|
||||||
# include <QClipboard>
|
# include <QClipboard>
|
||||||
# include <QColor>
|
# include <QColor>
|
||||||
# include <QComboBox>
|
# include <QComboBox>
|
||||||
# include <QCompleter>
|
|
||||||
# include <QCoreApplication>
|
|
||||||
# include <QDateTime>
|
# include <QDateTime>
|
||||||
# include <QDebug>
|
# include <QDebug>
|
||||||
# include <QDesktopServices>
|
# include <QDesktopServices>
|
||||||
# include <QDialog>
|
# include <QDialog>
|
||||||
# include <QDialogButtonBox>
|
# include <QDialogButtonBox>
|
||||||
# include <QDir>
|
# include <QDir>
|
||||||
# include <QDockWidget>
|
|
||||||
# include <QDrag>
|
|
||||||
# include <QDragEnterEvent>
|
|
||||||
# include <QElapsedTimer>
|
# include <QElapsedTimer>
|
||||||
# include <QEventLoop>
|
|
||||||
# include <QFile>
|
# include <QFile>
|
||||||
# include <QFileDialog>
|
# include <QFileDialog>
|
||||||
# include <QFileInfo>
|
# include <QFileInfo>
|
||||||
# include <QFlags>
|
# include <QFlags>
|
||||||
# include <QFont>
|
# include <QFont>
|
||||||
# include <QFontDatabase>
|
|
||||||
# include <QFontDialog>
|
|
||||||
# include <QFontMetrics>
|
# include <QFontMetrics>
|
||||||
# include <QFormLayout>
|
# include <QFormLayout>
|
||||||
# include <QGraphicsBlurEffect>
|
|
||||||
# include <QGroupBox>
|
# include <QGroupBox>
|
||||||
# include <QHBoxLayout>
|
# include <QHBoxLayout>
|
||||||
# include <QHeaderView>
|
# include <QHeaderView>
|
||||||
|
@ -58,7 +49,6 @@
|
||||||
# include <QKeyEvent>
|
# include <QKeyEvent>
|
||||||
# include <QLabel>
|
# include <QLabel>
|
||||||
# include <QLayout>
|
# include <QLayout>
|
||||||
# include <QLibrary>
|
|
||||||
# include <QLineEdit>
|
# include <QLineEdit>
|
||||||
# include <QList>
|
# include <QList>
|
||||||
# include <QListView>
|
# include <QListView>
|
||||||
|
@ -92,31 +82,17 @@
|
||||||
# include <QSizePolicy>
|
# include <QSizePolicy>
|
||||||
# include <QSlider>
|
# include <QSlider>
|
||||||
# include <QSpinBox>
|
# include <QSpinBox>
|
||||||
# include <QStackedLayout>
|
|
||||||
# include <QStandardPaths>
|
# include <QStandardPaths>
|
||||||
# include <QString>
|
# include <QString>
|
||||||
# include <QStyle>
|
# include <QStyle>
|
||||||
# include <QStyleOption>
|
# include <QStyleOption>
|
||||||
# include <QTabWidget>
|
# include <QTabWidget>
|
||||||
# include <QtCore/QVariant>
|
|
||||||
# include <QTextEdit>
|
# include <QTextEdit>
|
||||||
# include <QtGlobal>
|
# include <QtGlobal>
|
||||||
# include <QThread>
|
# include <QThread>
|
||||||
# include <QThreadPool>
|
# include <QThreadPool>
|
||||||
# include <QTime>
|
# include <QTime>
|
||||||
# include <QTimer>
|
# include <QTimer>
|
||||||
# include <QtWidgets/QApplication>
|
|
||||||
# include <QtWidgets/QButtonGroup>
|
|
||||||
# include <QtWidgets/QDialog>
|
|
||||||
# include <QtWidgets/QDialogButtonBox>
|
|
||||||
# include <QtWidgets/QFormLayout>
|
|
||||||
# include <QtWidgets/QHBoxLayout>
|
|
||||||
# include <QtWidgets/QHeaderView>
|
|
||||||
# include <QtWidgets/QLabel>
|
|
||||||
# include <QtWidgets/QLineEdit>
|
|
||||||
# include <QtWidgets/QPushButton>
|
|
||||||
# include <QtWidgets/QTabWidget>
|
|
||||||
# include <QtWidgets/QVBoxLayout>
|
|
||||||
# include <QUrl>
|
# include <QUrl>
|
||||||
# include <QUuid>
|
# include <QUuid>
|
||||||
# include <QVariant>
|
# include <QVariant>
|
||||||
|
|
|
@ -86,6 +86,13 @@ namespace {
|
||||||
QApplication::setWindowIcon(QIcon(":/icon.ico"));
|
QApplication::setWindowIcon(QIcon(":/icon.ico"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
// On the Mac/Cocoa platform this attribute is enabled by default
|
||||||
|
// We override it to ensure shortcuts show in context menus on that platform
|
||||||
|
QApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus,
|
||||||
|
false);
|
||||||
|
#endif
|
||||||
|
|
||||||
installCustomPalette();
|
installCustomPalette();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto *app = getIApp();
|
||||||
// Twitch channel
|
// Twitch channel
|
||||||
auto *tc = dynamic_cast<TwitchChannel *>(&this->channel_);
|
auto *tc = dynamic_cast<TwitchChannel *>(&this->channel_);
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto account = getApp()->accounts->twitch.getCurrent())
|
if (auto account = app->getAccounts()->twitch.getCurrent())
|
||||||
{
|
{
|
||||||
// Twitch Emotes available globally
|
// Twitch Emotes available globally
|
||||||
for (const auto &emote : account->accessEmotes()->emotes)
|
for (const auto &emote : account->accessEmotes()->emotes)
|
||||||
|
@ -153,18 +154,18 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
|
|
||||||
// 7TV Global
|
// 7TV Global
|
||||||
for (const auto &emote :
|
for (const auto &emote :
|
||||||
*getApp()->twitch->getSeventvEmotes().globalEmotes())
|
*app->getTwitch()->getSeventvEmotes().globalEmotes())
|
||||||
{
|
{
|
||||||
addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote);
|
addString(emote.first.string, TaggedString::Type::SeventvGlobalEmote);
|
||||||
}
|
}
|
||||||
// Bttv Global
|
// Bttv Global
|
||||||
for (const auto &emote : *getApp()->twitch->getBttvEmotes().emotes())
|
for (const auto &emote : *app->getTwitch()->getBttvEmotes().emotes())
|
||||||
{
|
{
|
||||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ffz Global
|
// Ffz Global
|
||||||
for (const auto &emote : *getApp()->twitch->getFfzEmotes().emotes())
|
for (const auto &emote : *app->getTwitch()->getFfzEmotes().emotes())
|
||||||
{
|
{
|
||||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||||
}
|
}
|
||||||
|
@ -172,7 +173,8 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
// Emojis
|
// Emojis
|
||||||
if (prefix.startsWith(":"))
|
if (prefix.startsWith(":"))
|
||||||
{
|
{
|
||||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
const auto &emojiShortCodes =
|
||||||
|
app->getEmotes()->getEmojis()->getShortCodes();
|
||||||
for (const auto &m : emojiShortCodes)
|
for (const auto &m : emojiShortCodes)
|
||||||
{
|
{
|
||||||
addString(QString(":%1:").arg(m), TaggedString::Type::Emoji);
|
addString(QString(":%1:").arg(m), TaggedString::Type::Emoji);
|
||||||
|
@ -231,20 +233,20 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||||
}
|
}
|
||||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||||
for (const auto &command : getApp()->commands->pluginCommands())
|
for (const auto &command : app->getCommands()->pluginCommands())
|
||||||
{
|
{
|
||||||
addString(command, TaggedString::PluginCommand);
|
addString(command, TaggedString::PluginCommand);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Custom Chatterino commands
|
// Custom Chatterino commands
|
||||||
for (const auto &command : getApp()->commands->items)
|
for (const auto &command : app->getCommands()->items)
|
||||||
{
|
{
|
||||||
addString(command.name, TaggedString::CustomCommand);
|
addString(command.name, TaggedString::CustomCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default Chatterino commands
|
// Default Chatterino commands
|
||||||
for (const auto &command :
|
for (const auto &command :
|
||||||
getApp()->commands->getDefaultChatterinoCommandList())
|
app->getCommands()->getDefaultChatterinoCommandList())
|
||||||
{
|
{
|
||||||
addString(command, TaggedString::ChatterinoCommand);
|
addString(command, TaggedString::ChatterinoCommand);
|
||||||
}
|
}
|
||||||
|
@ -256,6 +258,19 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<QString> CompletionModel::allItems() const
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->itemsMutex_);
|
||||||
|
|
||||||
|
std::vector<QString> results;
|
||||||
|
results.reserve(this->items_.size());
|
||||||
|
for (const auto &item : this->items_)
|
||||||
|
{
|
||||||
|
results.push_back(item.string);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
bool CompletionModel::compareStrings(const QString &a, const QString &b)
|
bool CompletionModel::compareStrings(const QString &a, const QString &b)
|
||||||
{
|
{
|
||||||
// try comparing insensitively, if they are the same then senstively
|
// try comparing insensitively, if they are the same then senstively
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
class InputCompletionTest;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Channel;
|
class Channel;
|
||||||
|
@ -60,10 +62,14 @@ public:
|
||||||
static bool compareStrings(const QString &a, const QString &b);
|
static bool compareStrings(const QString &a, const QString &b);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<QString> allItems() const;
|
||||||
|
|
||||||
mutable std::shared_mutex itemsMutex_;
|
mutable std::shared_mutex itemsMutex_;
|
||||||
std::set<TaggedString> items_;
|
std::set<TaggedString> items_;
|
||||||
|
|
||||||
Channel &channel_;
|
Channel &channel_;
|
||||||
|
|
||||||
|
friend class ::InputCompletionTest;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -197,9 +197,9 @@ void Credentials::get(const QString &provider, const QString &name_,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto &instance = insecureInstance();
|
const auto &instance = insecureInstance();
|
||||||
|
|
||||||
onLoaded(instance.object().find(name).value().toString());
|
onLoaded(instance[name].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
170
src/common/Literals.hpp
Normal file
170
src/common/Literals.hpp
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
/// This namespace defines the string suffixes _s, _ba, and _L1 used to create Qt types at compile-time.
|
||||||
|
/// They're easier to use comapred to their corresponding macros.
|
||||||
|
///
|
||||||
|
/// * u"foobar"_s creates a QString (like QStringLiteral). The u prefix is required.
|
||||||
|
///
|
||||||
|
/// * "foobar"_ba creates a QByteArray (like QByteArrayLiteral).
|
||||||
|
///
|
||||||
|
/// * "foobar"_L1 creates a QLatin1String(-View).
|
||||||
|
namespace chatterino::literals {
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
|
||||||
|
// This makes sure that the backing data never causes allocation after compilation.
|
||||||
|
// It's essentially the QStringLiteral macro inlined.
|
||||||
|
//
|
||||||
|
// From desktop-app/lib_base
|
||||||
|
// https://github.com/desktop-app/lib_base/blob/f904c60987115a4b514a575b23009ff25de0fafa/base/basic_types.h#L63-L152
|
||||||
|
// And qt/qtbase (5.15)
|
||||||
|
// https://github.com/qt/qtbase/blob/29400a683f96867133b28299c0d0bd6bcf40df35/src/corelib/text/qstringliteral.h#L64-L104
|
||||||
|
namespace detail {
|
||||||
|
// NOLINTBEGIN(modernize-avoid-c-arrays)
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays)
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
struct LiteralResolver {
|
||||||
|
template <size_t... I>
|
||||||
|
constexpr LiteralResolver(const char16_t (&text)[N],
|
||||||
|
std::index_sequence<I...> /*seq*/)
|
||||||
|
: utf16Text{text[I]...}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
template <size_t... I>
|
||||||
|
constexpr LiteralResolver(const char (&text)[N],
|
||||||
|
std::index_sequence<I...> /*seq*/)
|
||||||
|
: latin1Text{text[I]...}
|
||||||
|
, latin1(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
constexpr LiteralResolver(const char16_t (&text)[N])
|
||||||
|
: LiteralResolver(text, std::make_index_sequence<N>{})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
constexpr LiteralResolver(const char (&text)[N])
|
||||||
|
: LiteralResolver(text, std::make_index_sequence<N>{})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const char16_t utf16Text[N]{};
|
||||||
|
const char latin1Text[N]{};
|
||||||
|
size_t length = N;
|
||||||
|
bool latin1 = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
struct StaticStringData {
|
||||||
|
template <std::size_t... I>
|
||||||
|
constexpr StaticStringData(const char16_t (&text)[N],
|
||||||
|
std::index_sequence<I...> /*seq*/)
|
||||||
|
: data Q_STATIC_STRING_DATA_HEADER_INITIALIZER(N - 1)
|
||||||
|
, text{text[I]...}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QArrayData data;
|
||||||
|
char16_t text[N];
|
||||||
|
|
||||||
|
QStringData *pointer()
|
||||||
|
{
|
||||||
|
Q_ASSERT(data.ref.isStatic());
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||||
|
return static_cast<QStringData *>(&data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
struct StaticByteArrayData {
|
||||||
|
template <std::size_t... I>
|
||||||
|
constexpr StaticByteArrayData(const char (&text)[N],
|
||||||
|
std::index_sequence<I...> /*seq*/)
|
||||||
|
: data Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER(N - 1)
|
||||||
|
, text{text[I]...}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QByteArrayData data;
|
||||||
|
char text[N];
|
||||||
|
|
||||||
|
QByteArrayData *pointer()
|
||||||
|
{
|
||||||
|
Q_ASSERT(data.ref.isStatic());
|
||||||
|
return &data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOLINTEND(cppcoreguidelines-avoid-c-arrays)
|
||||||
|
// NOLINTEND(modernize-avoid-c-arrays)
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <detail::LiteralResolver R>
|
||||||
|
inline QString operator""_s() noexcept
|
||||||
|
{
|
||||||
|
static_assert(R.length > 0); // always has a terminating null
|
||||||
|
static_assert(!R.latin1, "QString literals must be made up of 16bit "
|
||||||
|
"characters. Forgot a u\"\"?");
|
||||||
|
|
||||||
|
static auto literal = detail::StaticStringData<R.length>(
|
||||||
|
R.utf16Text, std::make_index_sequence<R.length>{});
|
||||||
|
return QString{QStringDataPtr{literal.pointer()}};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <detail::LiteralResolver R>
|
||||||
|
inline QByteArray operator""_ba() noexcept
|
||||||
|
{
|
||||||
|
static_assert(R.length > 0); // always has a terminating null
|
||||||
|
static_assert(R.latin1, "QByteArray literals must be made up of 8bit "
|
||||||
|
"characters. Misplaced u\"\"?");
|
||||||
|
|
||||||
|
static auto literal = detail::StaticByteArrayData<R.length>(
|
||||||
|
R.latin1Text, std::make_index_sequence<R.length>{});
|
||||||
|
return QByteArray{QByteArrayDataPtr{literal.pointer()}};
|
||||||
|
};
|
||||||
|
|
||||||
|
#elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
|
||||||
|
|
||||||
|
// The operators were added in 6.4, but their implementation works in any 6.x version.
|
||||||
|
//
|
||||||
|
// NOLINTBEGIN(cppcoreguidelines-pro-type-const-cast)
|
||||||
|
inline QString operator""_s(const char16_t *str, size_t size) noexcept
|
||||||
|
{
|
||||||
|
return QString(
|
||||||
|
QStringPrivate(nullptr, const_cast<char16_t *>(str), qsizetype(size)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QByteArray operator""_ba(const char *str, size_t size) noexcept
|
||||||
|
{
|
||||||
|
return QByteArray(
|
||||||
|
QByteArrayData(nullptr, const_cast<char *>(str), qsizetype(size)));
|
||||||
|
}
|
||||||
|
// NOLINTEND(cppcoreguidelines-pro-type-const-cast)
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline QString operator""_s(const char16_t *str, size_t size) noexcept
|
||||||
|
{
|
||||||
|
return Qt::Literals::StringLiterals::operator""_s(str, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QByteArray operator""_ba(const char *str, size_t size) noexcept
|
||||||
|
{
|
||||||
|
return Qt::Literals::StringLiterals::operator""_ba(str, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr inline QLatin1String operator""_L1(const char *str,
|
||||||
|
size_t size) noexcept
|
||||||
|
{
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
using SizeType = int;
|
||||||
|
#else
|
||||||
|
using SizeType = qsizetype;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return QLatin1String{str, static_cast<SizeType>(size)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::literals
|
|
@ -72,12 +72,12 @@ void writeToCache(const std::shared_ptr<NetworkData> &data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadUncached(const std::shared_ptr<NetworkData> &data)
|
void loadUncached(std::shared_ptr<NetworkData> &&data)
|
||||||
{
|
{
|
||||||
DebugCount::increase("http request started");
|
DebugCount::increase("http request started");
|
||||||
|
|
||||||
NetworkRequester requester;
|
NetworkRequester requester;
|
||||||
NetworkWorker *worker = new NetworkWorker;
|
auto *worker = new NetworkWorker;
|
||||||
|
|
||||||
worker->moveToThread(&NetworkManager::workerThread);
|
worker->moveToThread(&NetworkManager::workerThread);
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
data->timer_->start(data->timeoutMS_);
|
data->timer_->start(data->timeoutMS_);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto reply = [&]() -> QNetworkReply * {
|
auto *reply = [&]() -> QNetworkReply * {
|
||||||
switch (data->requestType_)
|
switch (data->requestType_)
|
||||||
{
|
{
|
||||||
case NetworkRequestType::Get:
|
case NetworkRequestType::Get:
|
||||||
|
@ -155,7 +155,8 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
{
|
{
|
||||||
postToThread([data] {
|
postToThread([data] {
|
||||||
data->onError_(NetworkResult(
|
data->onError_(NetworkResult(
|
||||||
{}, NetworkResult::timedoutStatus));
|
NetworkResult::NetworkError::TimeoutError, {},
|
||||||
|
{}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +175,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handleReply = [data, reply]() mutable {
|
auto handleReply = [data, reply]() mutable {
|
||||||
if (data->hasCaller_ && !data->caller_.get())
|
if (data->hasCaller_ && data->caller_.isNull())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -218,8 +219,9 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
QString(data->payload_));
|
QString(data->payload_));
|
||||||
}
|
}
|
||||||
// TODO: Should this always be run on the GUI thread?
|
// TODO: Should this always be run on the GUI thread?
|
||||||
postToThread([data, code = status.toInt(), reply] {
|
postToThread([data, status, reply] {
|
||||||
data->onError_(NetworkResult(reply->readAll(), code));
|
data->onError_(NetworkResult(reply->error(), status,
|
||||||
|
reply->readAll()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,19 +240,23 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
auto status =
|
auto status =
|
||||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
|
|
||||||
NetworkResult result(bytes, status.toInt());
|
NetworkResult result(reply->error(), status, bytes);
|
||||||
|
|
||||||
DebugCount::increase("http request success");
|
DebugCount::increase("http request success");
|
||||||
// log("starting {}", data->request_.url().toString());
|
// log("starting {}", data->request_.url().toString());
|
||||||
if (data->onSuccess_)
|
if (data->onSuccess_)
|
||||||
{
|
{
|
||||||
if (data->executeConcurrently_)
|
if (data->executeConcurrently_)
|
||||||
|
{
|
||||||
QtConcurrent::run([onSuccess = std::move(data->onSuccess_),
|
QtConcurrent::run([onSuccess = std::move(data->onSuccess_),
|
||||||
result = std::move(result)] {
|
result = std::move(result)] {
|
||||||
onSuccess(result);
|
onSuccess(result);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
data->onSuccess_(result);
|
data->onSuccess_(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// log("finished {}", data->request_.url().toString());
|
// log("finished {}", data->request_.url().toString());
|
||||||
|
|
||||||
|
@ -276,11 +282,15 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
if (data->finally_)
|
if (data->finally_)
|
||||||
{
|
{
|
||||||
if (data->executeConcurrently_)
|
if (data->executeConcurrently_)
|
||||||
|
{
|
||||||
QtConcurrent::run([finally = std::move(data->finally_)] {
|
QtConcurrent::run([finally = std::move(data->finally_)] {
|
||||||
finally();
|
finally();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
data->finally_();
|
data->finally_();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -316,87 +326,88 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First tried to load cached, then uncached.
|
// First tried to load cached, then uncached.
|
||||||
void loadCached(const std::shared_ptr<NetworkData> &data)
|
void loadCached(std::shared_ptr<NetworkData> &&data)
|
||||||
{
|
{
|
||||||
QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash());
|
QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash());
|
||||||
|
|
||||||
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
|
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
|
||||||
{
|
{
|
||||||
// File didn't exist OR File could not be opened
|
// File didn't exist OR File could not be opened
|
||||||
loadUncached(data);
|
loadUncached(std::move(data));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// XXX: check if bytes is empty?
|
|
||||||
QByteArray bytes = cachedFile.readAll();
|
|
||||||
NetworkResult result(bytes, 200);
|
|
||||||
|
|
||||||
qCDebug(chatterinoHTTP)
|
// XXX: check if bytes is empty?
|
||||||
<< QString("%1 [CACHED] 200 %2")
|
QByteArray bytes = cachedFile.readAll();
|
||||||
.arg(networkRequestTypes.at(int(data->requestType_)),
|
NetworkResult result(NetworkResult::NetworkError::NoError, QVariant(200),
|
||||||
data->request_.url().toString());
|
bytes);
|
||||||
if (data->onSuccess_)
|
|
||||||
|
qCDebug(chatterinoHTTP)
|
||||||
|
<< QString("%1 [CACHED] 200 %2")
|
||||||
|
.arg(networkRequestTypes.at(int(data->requestType_)),
|
||||||
|
data->request_.url().toString());
|
||||||
|
if (data->onSuccess_)
|
||||||
|
{
|
||||||
|
if (data->executeConcurrently_ || isGuiThread())
|
||||||
{
|
{
|
||||||
if (data->executeConcurrently_ || isGuiThread())
|
// XXX: If outcome is Failure, we should invalidate the cache file
|
||||||
|
// somehow/somewhere
|
||||||
|
/*auto outcome =*/
|
||||||
|
if (data->hasCaller_ && data->caller_.isNull())
|
||||||
{
|
{
|
||||||
// XXX: If outcome is Failure, we should invalidate the cache file
|
return;
|
||||||
// somehow/somewhere
|
}
|
||||||
/*auto outcome =*/
|
data->onSuccess_(result);
|
||||||
if (data->hasCaller_ && !data->caller_.get())
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
postToThread([data, result]() {
|
||||||
|
if (data->hasCaller_ && data->caller_.isNull())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->onSuccess_(result);
|
data->onSuccess_(result);
|
||||||
}
|
});
|
||||||
else
|
|
||||||
{
|
|
||||||
postToThread([data, result]() {
|
|
||||||
if (data->hasCaller_ && !data->caller_.get())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->onSuccess_(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data->finally_)
|
if (data->finally_)
|
||||||
|
{
|
||||||
|
if (data->executeConcurrently_ || isGuiThread())
|
||||||
{
|
{
|
||||||
if (data->executeConcurrently_ || isGuiThread())
|
if (data->hasCaller_ && data->caller_.isNull())
|
||||||
{
|
{
|
||||||
if (data->hasCaller_ && !data->caller_.get())
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->finally_();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
postToThread([data]() {
|
||||||
|
if (data->hasCaller_ && data->caller_.isNull())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
data->finally_();
|
data->finally_();
|
||||||
}
|
});
|
||||||
else
|
|
||||||
{
|
|
||||||
postToThread([data]() {
|
|
||||||
if (data->hasCaller_ && !data->caller_.get())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->finally_();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load(const std::shared_ptr<NetworkData> &data)
|
void load(std::shared_ptr<NetworkData> &&data)
|
||||||
{
|
{
|
||||||
if (data->cache_)
|
if (data->cache_)
|
||||||
{
|
{
|
||||||
QtConcurrent::run(loadCached, data);
|
QtConcurrent::run([data = std::move(data)]() mutable {
|
||||||
|
loadCached(std::move(data));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
loadUncached(data);
|
loadUncached(std::move(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/NetworkCommon.hpp"
|
#include "common/NetworkCommon.hpp"
|
||||||
#include "util/QObjectRef.hpp"
|
|
||||||
|
|
||||||
#include <QHttpMultiPart>
|
#include <QHttpMultiPart>
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
|
#include <QPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -38,7 +38,7 @@ struct NetworkData {
|
||||||
|
|
||||||
QNetworkRequest request_;
|
QNetworkRequest request_;
|
||||||
bool hasCaller_{};
|
bool hasCaller_{};
|
||||||
QObjectRef<QObject> caller_;
|
QPointer<QObject> caller_;
|
||||||
bool cache_{};
|
bool cache_{};
|
||||||
bool executeConcurrently_{};
|
bool executeConcurrently_{};
|
||||||
|
|
||||||
|
@ -68,6 +68,6 @@ private:
|
||||||
QString hash_;
|
QString hash_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void load(const std::shared_ptr<NetworkData> &data);
|
void load(std::shared_ptr<NetworkData> &&data);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
|
|
||||||
#include "common/NetworkPrivate.hpp"
|
#include "common/NetworkPrivate.hpp"
|
||||||
#include "common/Outcome.hpp"
|
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
|
||||||
#include "util/DebugCount.hpp"
|
|
||||||
#include "util/PostToThread.hpp"
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
@ -28,7 +22,7 @@ NetworkRequest::NetworkRequest(const std::string &url,
|
||||||
this->initializeDefaultValues();
|
this->initializeDefaultValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType)
|
NetworkRequest::NetworkRequest(const QUrl &url, NetworkRequestType requestType)
|
||||||
: data(new NetworkData)
|
: data(new NetworkData)
|
||||||
{
|
{
|
||||||
this->data->request_.setUrl(url);
|
this->data->request_.setUrl(url);
|
||||||
|
@ -37,10 +31,7 @@ NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType)
|
||||||
this->initializeDefaultValues();
|
this->initializeDefaultValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest::~NetworkRequest()
|
NetworkRequest::~NetworkRequest() = default;
|
||||||
{
|
|
||||||
//assert(!this->data || this->executed_);
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::type(NetworkRequestType newRequestType) &&
|
NetworkRequest NetworkRequest::type(NetworkRequestType newRequestType) &&
|
||||||
{
|
{
|
||||||
|
@ -63,25 +54,25 @@ NetworkRequest NetworkRequest::caller(const QObject *caller) &&
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::onReplyCreated(NetworkReplyCreatedCallback cb) &&
|
NetworkRequest NetworkRequest::onReplyCreated(NetworkReplyCreatedCallback cb) &&
|
||||||
{
|
{
|
||||||
this->data->onReplyCreated_ = cb;
|
this->data->onReplyCreated_ = std::move(cb);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::onError(NetworkErrorCallback cb) &&
|
NetworkRequest NetworkRequest::onError(NetworkErrorCallback cb) &&
|
||||||
{
|
{
|
||||||
this->data->onError_ = cb;
|
this->data->onError_ = std::move(cb);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) &&
|
NetworkRequest NetworkRequest::onSuccess(NetworkSuccessCallback cb) &&
|
||||||
{
|
{
|
||||||
this->data->onSuccess_ = cb;
|
this->data->onSuccess_ = std::move(cb);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) &&
|
NetworkRequest NetworkRequest::finally(NetworkFinallyCallback cb) &&
|
||||||
{
|
{
|
||||||
this->data->finally_ = cb;
|
this->data->finally_ = std::move(cb);
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +97,13 @@ NetworkRequest NetworkRequest::header(const char *headerName,
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkRequest NetworkRequest::header(QNetworkRequest::KnownHeaders header,
|
||||||
|
const QVariant &value) &&
|
||||||
|
{
|
||||||
|
this->data->request_.setHeader(header, value);
|
||||||
|
return std::move(*this);
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::headerList(
|
NetworkRequest NetworkRequest::headerList(
|
||||||
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&
|
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&
|
||||||
{
|
{
|
||||||
|
@ -129,20 +127,6 @@ NetworkRequest NetworkRequest::concurrent() &&
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::authorizeTwitchV5(const QString &clientID,
|
|
||||||
const QString &oauthToken) &&
|
|
||||||
{
|
|
||||||
// TODO: make two overloads, with and without oauth token
|
|
||||||
auto tmp = std::move(*this)
|
|
||||||
.header("Client-ID", clientID)
|
|
||||||
.header("Accept", "application/vnd.twitchtv.v5+json");
|
|
||||||
|
|
||||||
if (!oauthToken.isEmpty())
|
|
||||||
return std::move(tmp).header("Authorization", "OAuth " + oauthToken);
|
|
||||||
else
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkRequest NetworkRequest::multiPart(QHttpMultiPart *payload) &&
|
NetworkRequest NetworkRequest::multiPart(QHttpMultiPart *payload) &&
|
||||||
{
|
{
|
||||||
payload->setParent(this->data->lifetimeManager_);
|
payload->setParent(this->data->lifetimeManager_);
|
||||||
|
@ -200,17 +184,36 @@ void NetworkRequest::execute()
|
||||||
|
|
||||||
void NetworkRequest::initializeDefaultValues()
|
void NetworkRequest::initializeDefaultValues()
|
||||||
{
|
{
|
||||||
const auto userAgent = QString("chatterino/%1 (%2)")
|
const auto userAgent = QStringLiteral("chatterino/%1 (%2)")
|
||||||
.arg(CHATTERINO_VERSION, CHATTERINO_GIT_HASH)
|
.arg(Version::instance().version(),
|
||||||
|
Version::instance().commitHash())
|
||||||
.toUtf8();
|
.toUtf8();
|
||||||
|
|
||||||
this->data->request_.setRawHeader("User-Agent", userAgent);
|
this->data->request_.setRawHeader("User-Agent", userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper creator functions
|
NetworkRequest NetworkRequest::json(const QJsonArray &root) &&
|
||||||
NetworkRequest NetworkRequest::twitchRequest(QUrl url)
|
|
||||||
{
|
{
|
||||||
return NetworkRequest(url).authorizeTwitchV5(getDefaultClientID());
|
return std::move(*this).json(QJsonDocument(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkRequest NetworkRequest::json(const QJsonObject &root) &&
|
||||||
|
{
|
||||||
|
return std::move(*this).json(QJsonDocument(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkRequest NetworkRequest::json(const QJsonDocument &document) &&
|
||||||
|
{
|
||||||
|
return std::move(*this).json(document.toJson(QJsonDocument::Compact));
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkRequest NetworkRequest::json(const QByteArray &payload) &&
|
||||||
|
{
|
||||||
|
return std::move(*this)
|
||||||
|
.payload(payload)
|
||||||
|
.header(QNetworkRequest::ContentTypeHeader, "application/json")
|
||||||
|
.header(QNetworkRequest::ContentLengthHeader, payload.length())
|
||||||
|
.header("Accept", "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
class QJsonArray;
|
||||||
|
class QJsonObject;
|
||||||
|
class QJsonDocument;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct NetworkData;
|
struct NetworkData;
|
||||||
|
@ -24,8 +28,8 @@ public:
|
||||||
explicit NetworkRequest(
|
explicit NetworkRequest(
|
||||||
const std::string &url,
|
const std::string &url,
|
||||||
NetworkRequestType requestType = NetworkRequestType::Get);
|
NetworkRequestType requestType = NetworkRequestType::Get);
|
||||||
explicit NetworkRequest(
|
explicit NetworkRequest(const QUrl &url, NetworkRequestType requestType =
|
||||||
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
|
NetworkRequestType::Get);
|
||||||
|
|
||||||
// Enable move
|
// Enable move
|
||||||
NetworkRequest(NetworkRequest &&other) = default;
|
NetworkRequest(NetworkRequest &&other) = default;
|
||||||
|
@ -54,23 +58,25 @@ public:
|
||||||
NetworkRequest header(const char *headerName, const char *value) &&;
|
NetworkRequest header(const char *headerName, const char *value) &&;
|
||||||
NetworkRequest header(const char *headerName, const QByteArray &value) &&;
|
NetworkRequest header(const char *headerName, const QByteArray &value) &&;
|
||||||
NetworkRequest header(const char *headerName, const QString &value) &&;
|
NetworkRequest header(const char *headerName, const QString &value) &&;
|
||||||
|
NetworkRequest header(QNetworkRequest::KnownHeaders header,
|
||||||
|
const QVariant &value) &&;
|
||||||
NetworkRequest headerList(
|
NetworkRequest headerList(
|
||||||
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&;
|
const std::vector<std::pair<QByteArray, QByteArray>> &headers) &&;
|
||||||
NetworkRequest timeout(int ms) &&;
|
NetworkRequest timeout(int ms) &&;
|
||||||
NetworkRequest concurrent() &&;
|
NetworkRequest concurrent() &&;
|
||||||
NetworkRequest authorizeTwitchV5(const QString &clientID,
|
|
||||||
const QString &oauthToken = QString()) &&;
|
|
||||||
NetworkRequest multiPart(QHttpMultiPart *payload) &&;
|
NetworkRequest multiPart(QHttpMultiPart *payload) &&;
|
||||||
/**
|
/**
|
||||||
* This will change `RedirectPolicyAttribute`.
|
* This will change `RedirectPolicyAttribute`.
|
||||||
* `QNetworkRequest`'s defaults are used by default (Qt 5: no-follow, Qt 6: follow).
|
* `QNetworkRequest`'s defaults are used by default (Qt 5: no-follow, Qt 6: follow).
|
||||||
*/
|
*/
|
||||||
NetworkRequest followRedirects(bool on) &&;
|
NetworkRequest followRedirects(bool on) &&;
|
||||||
|
NetworkRequest json(const QJsonObject &root) &&;
|
||||||
|
NetworkRequest json(const QJsonArray &root) &&;
|
||||||
|
NetworkRequest json(const QJsonDocument &document) &&;
|
||||||
|
NetworkRequest json(const QByteArray &payload) &&;
|
||||||
|
|
||||||
void execute();
|
void execute();
|
||||||
|
|
||||||
static NetworkRequest twitchRequest(QUrl url);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initializeDefaultValues();
|
void initializeDefaultValues();
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,15 +3,21 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QMetaEnum>
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/error/en.h>
|
#include <rapidjson/error/en.h>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
NetworkResult::NetworkResult(const QByteArray &data, int status)
|
NetworkResult::NetworkResult(NetworkError error, const QVariant &httpStatusCode,
|
||||||
: data_(data)
|
QByteArray data)
|
||||||
, status_(status)
|
: data_(std::move(data))
|
||||||
|
, error_(error)
|
||||||
{
|
{
|
||||||
|
if (httpStatusCode.isValid())
|
||||||
|
{
|
||||||
|
this->status_ = httpStatusCode.toInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject NetworkResult::parseJson() const
|
QJsonObject NetworkResult::parseJson() const
|
||||||
|
@ -59,9 +65,21 @@ const QByteArray &NetworkResult::getData() const
|
||||||
return this->data_;
|
return this->data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
int NetworkResult::status() const
|
QString NetworkResult::formatError() const
|
||||||
{
|
{
|
||||||
return this->status_;
|
if (this->status_)
|
||||||
|
{
|
||||||
|
return QString::number(*this->status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto *name =
|
||||||
|
QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(
|
||||||
|
this->error_);
|
||||||
|
if (name == nullptr)
|
||||||
|
{
|
||||||
|
return QStringLiteral("unknown error (%1)").arg(this->error_);
|
||||||
|
}
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -2,14 +2,20 @@
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QNetworkReply>
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class NetworkResult
|
class NetworkResult
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NetworkResult(const QByteArray &data, int status);
|
using NetworkError = QNetworkReply::NetworkError;
|
||||||
|
|
||||||
|
NetworkResult(NetworkError error, const QVariant &httpStatusCode,
|
||||||
|
QByteArray data);
|
||||||
|
|
||||||
/// Parses the result as json and returns the root as an object.
|
/// Parses the result as json and returns the root as an object.
|
||||||
/// Returns empty object if parsing failed.
|
/// Returns empty object if parsing failed.
|
||||||
|
@ -20,13 +26,29 @@ public:
|
||||||
/// Parses the result as json and returns the document.
|
/// Parses the result as json and returns the document.
|
||||||
rapidjson::Document parseRapidJson() const;
|
rapidjson::Document parseRapidJson() const;
|
||||||
const QByteArray &getData() const;
|
const QByteArray &getData() const;
|
||||||
int status() const;
|
|
||||||
|
|
||||||
static constexpr int timedoutStatus = -2;
|
/// The error code of the reply.
|
||||||
|
/// In case of a successful reply, this will be NoError (0)
|
||||||
|
NetworkError error() const
|
||||||
|
{
|
||||||
|
return this->error_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The HTTP status code if a response was received.
|
||||||
|
std::optional<int> status() const
|
||||||
|
{
|
||||||
|
return this->status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats the error.
|
||||||
|
/// If a reply is received, returns the HTTP status otherwise, the network error.
|
||||||
|
QString formatError() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray data_;
|
QByteArray data_;
|
||||||
int status_;
|
|
||||||
|
NetworkError error_;
|
||||||
|
std::optional<int> status_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -48,8 +48,11 @@ Q_LOGGING_CATEGORY(chatterinoStreamlink, "chatterino.streamlink", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoTheme, "chatterino.theme", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoTheme, "chatterino.theme", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoTokenizer, "chatterino.tokenizer", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoTokenizer, "chatterino.tokenizer", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoTwitch, "chatterino.twitch", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoTwitch, "chatterino.twitch", logThreshold);
|
||||||
|
Q_LOGGING_CATEGORY(chatterinoTwitchLiveController,
|
||||||
|
"chatterino.twitch.livecontroller", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoUpdate, "chatterino.update", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoUpdate, "chatterino.update", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoWebsocket, "chatterino.websocket", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoWebsocket, "chatterino.websocket", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold);
|
Q_LOGGING_CATEGORY(chatterinoWidget, "chatterino.widget", logThreshold);
|
||||||
Q_LOGGING_CATEGORY(chatterinoWindowmanager, "chatterino.windowmanager",
|
Q_LOGGING_CATEGORY(chatterinoWindowmanager, "chatterino.windowmanager",
|
||||||
logThreshold);
|
logThreshold);
|
||||||
|
Q_LOGGING_CATEGORY(chatterinoXDG, "chatterino.xdg", logThreshold);
|
||||||
|
|
|
@ -37,7 +37,9 @@ Q_DECLARE_LOGGING_CATEGORY(chatterinoStreamlink);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoTheme);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoTheme);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoTokenizer);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitch);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitch);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoTwitchLiveController);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoUpdate);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoUpdate);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWebsocket);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWidget);
|
||||||
Q_DECLARE_LOGGING_CATEGORY(chatterinoWindowmanager);
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoWindowmanager);
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(chatterinoXDG);
|
||||||
|
|
|
@ -4,27 +4,14 @@
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
#define UGLYMACROHACK1(s) #s
|
|
||||||
#define FROM_EXTERNAL_DEFINE(s) UGLYMACROHACK1(s)
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
Version::Version()
|
Version::Version()
|
||||||
|
: version_(CHATTERINO_VERSION)
|
||||||
|
, commitHash_(QStringLiteral(CHATTERINO_GIT_HASH))
|
||||||
|
, isModified_(CHATTERINO_GIT_MODIFIED == 1)
|
||||||
|
, dateOfBuild_(QStringLiteral(CHATTERINO_CMAKE_GEN_DATE))
|
||||||
{
|
{
|
||||||
this->version_ = CHATTERINO_VERSION;
|
|
||||||
|
|
||||||
this->commitHash_ =
|
|
||||||
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_GIT_HASH)).remove('"');
|
|
||||||
|
|
||||||
#ifdef CHATTERINO_GIT_MODIFIED
|
|
||||||
this->isModified_ = true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef CHATTERINO_CMAKE_GEN_DATE
|
|
||||||
this->dateOfBuild_ =
|
|
||||||
QString(FROM_EXTERNAL_DEFINE(CHATTERINO_CMAKE_GEN_DATE)).remove('"');
|
|
||||||
#endif
|
|
||||||
|
|
||||||
this->fullVersion_ = "Chatterino ";
|
this->fullVersion_ = "Chatterino ";
|
||||||
if (Modes::instance().isNightly)
|
if (Modes::instance().isNightly)
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
* - 2.4.0-alpha.2
|
* - 2.4.0-alpha.2
|
||||||
* - 2.4.0-alpha
|
* - 2.4.0-alpha
|
||||||
**/
|
**/
|
||||||
#define CHATTERINO_VERSION "2.4.3"
|
#define CHATTERINO_VERSION "2.4.4"
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
# define CHATTERINO_OS "win"
|
# define CHATTERINO_OS "win"
|
||||||
|
|
|
@ -7,13 +7,16 @@
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "common/SignalVector.hpp"
|
#include "common/SignalVector.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
|
#include "controllers/commands/builtin/chatterino/Debugging.hpp"
|
||||||
#include "controllers/commands/builtin/twitch/ChatSettings.hpp"
|
#include "controllers/commands/builtin/twitch/ChatSettings.hpp"
|
||||||
#include "controllers/commands/builtin/twitch/ShieldMode.hpp"
|
#include "controllers/commands/builtin/twitch/ShieldMode.hpp"
|
||||||
|
#include "controllers/commands/builtin/twitch/Shoutout.hpp"
|
||||||
#include "controllers/commands/Command.hpp"
|
#include "controllers/commands/Command.hpp"
|
||||||
#include "controllers/commands/CommandContext.hpp"
|
#include "controllers/commands/CommandContext.hpp"
|
||||||
#include "controllers/commands/CommandModel.hpp"
|
#include "controllers/commands/CommandModel.hpp"
|
||||||
#include "controllers/plugins/PluginController.hpp"
|
#include "controllers/plugins/PluginController.hpp"
|
||||||
#include "controllers/userdata/UserDataController.hpp"
|
#include "controllers/userdata/UserDataController.hpp"
|
||||||
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -36,6 +39,7 @@
|
||||||
#include "util/FormatTime.hpp"
|
#include "util/FormatTime.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
|
#include "util/PostToThread.hpp"
|
||||||
#include "util/Qt.hpp"
|
#include "util/Qt.hpp"
|
||||||
#include "util/StreamerMode.hpp"
|
#include "util/StreamerMode.hpp"
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
|
@ -646,7 +650,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||||
target,
|
target,
|
||||||
[currentUser, channel, target](const HelixUser &targetUser) {
|
[currentUser, channel, target](const HelixUser &targetUser) {
|
||||||
getApp()->accounts->twitch.getCurrent()->blockUser(
|
getApp()->accounts->twitch.getCurrent()->blockUser(
|
||||||
targetUser.id,
|
targetUser.id, nullptr,
|
||||||
[channel, target, targetUser] {
|
[channel, target, targetUser] {
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
QString("You successfully blocked user %1")
|
QString("You successfully blocked user %1")
|
||||||
|
@ -699,7 +703,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||||
target,
|
target,
|
||||||
[currentUser, channel, target](const auto &targetUser) {
|
[currentUser, channel, target](const auto &targetUser) {
|
||||||
getApp()->accounts->twitch.getCurrent()->unblockUser(
|
getApp()->accounts->twitch.getCurrent()->unblockUser(
|
||||||
targetUser.id,
|
targetUser.id, nullptr,
|
||||||
[channel, target, targetUser] {
|
[channel, target, targetUser] {
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
QString("You successfully unblocked user %1")
|
QString("You successfully unblocked user %1")
|
||||||
|
@ -919,7 +923,8 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||||
static_cast<QWidget *>(&(getApp()->windows->getMainWindow())),
|
static_cast<QWidget *>(&(getApp()->windows->getMainWindow())),
|
||||||
currentSplit);
|
currentSplit);
|
||||||
userPopup->setData(userName, channel);
|
userPopup->setData(userName, channel);
|
||||||
userPopup->move(QCursor::pos());
|
userPopup->moveTo(QCursor::pos(), false,
|
||||||
|
BaseWindow::BoundsChecker::CursorPosition);
|
||||||
userPopup->show();
|
userPopup->show();
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
@ -3209,8 +3214,35 @@ void CommandController::initialize(Settings &, Paths &paths)
|
||||||
return "";
|
return "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this->registerCommand(
|
||||||
|
"/debug-force-image-gc",
|
||||||
|
[](const QStringList & /*words*/, auto /*channel*/) -> QString {
|
||||||
|
runInGuiThread([] {
|
||||||
|
using namespace chatterino::detail;
|
||||||
|
auto &iep = ImageExpirationPool::instance();
|
||||||
|
iep.freeOld();
|
||||||
|
});
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
this->registerCommand(
|
||||||
|
"/debug-force-image-unload",
|
||||||
|
[](const QStringList & /*words*/, auto /*channel*/) -> QString {
|
||||||
|
runInGuiThread([] {
|
||||||
|
using namespace chatterino::detail;
|
||||||
|
auto &iep = ImageExpirationPool::instance();
|
||||||
|
iep.freeAll();
|
||||||
|
});
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
this->registerCommand("/shield", &commands::shieldModeOn);
|
this->registerCommand("/shield", &commands::shieldModeOn);
|
||||||
this->registerCommand("/shieldoff", &commands::shieldModeOff);
|
this->registerCommand("/shieldoff", &commands::shieldModeOff);
|
||||||
|
|
||||||
|
this->registerCommand("/shoutout", &commands::sendShoutout);
|
||||||
|
|
||||||
|
this->registerCommand("/c2-set-logging-rules", &commands::setLoggingRules);
|
||||||
|
this->registerCommand("/c2-theme-autoreload", &commands::toggleThemeReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandController::save()
|
void CommandController::save()
|
||||||
|
|
66
src/controllers/commands/builtin/chatterino/Debugging.cpp
Normal file
66
src/controllers/commands/builtin/chatterino/Debugging.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include "controllers/commands/builtin/chatterino/Debugging.hpp"
|
||||||
|
|
||||||
|
#include "common/Channel.hpp"
|
||||||
|
#include "common/Literals.hpp"
|
||||||
|
#include "controllers/commands/CommandContext.hpp"
|
||||||
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
|
#include <QLoggingCategory>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace chatterino::commands {
|
||||||
|
|
||||||
|
using namespace literals;
|
||||||
|
|
||||||
|
QString setLoggingRules(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
if (ctx.words.size() < 2)
|
||||||
|
{
|
||||||
|
ctx.channel->addMessage(makeSystemMessage(
|
||||||
|
"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"));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filterRules = ctx.words.mid(1).join('\n');
|
||||||
|
|
||||||
|
QLoggingCategory::setFilterRules(filterRules);
|
||||||
|
|
||||||
|
auto message =
|
||||||
|
QStringLiteral("Updated filter rules to '%1'.").arg(filterRules);
|
||||||
|
|
||||||
|
if (!qgetenv("QT_LOGGING_RULES").isEmpty())
|
||||||
|
{
|
||||||
|
message += QStringLiteral(
|
||||||
|
" Warning: Logging rules were previously set by the "
|
||||||
|
"QT_LOGGING_RULES environment variable. This might cause "
|
||||||
|
"interference - see: "
|
||||||
|
"https://doc.qt.io/qt-6/qloggingcategory.html#setFilterRules");
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.channel->addMessage(makeSystemMessage(message));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toggleThemeReload(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
if (getTheme()->isAutoReloading())
|
||||||
|
{
|
||||||
|
getTheme()->setAutoReload(false);
|
||||||
|
ctx.channel->addMessage(
|
||||||
|
makeSystemMessage(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)));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::commands
|
17
src/controllers/commands/builtin/chatterino/Debugging.hpp
Normal file
17
src/controllers/commands/builtin/chatterino/Debugging.hpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class QString;
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct CommandContext;
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
|
||||||
|
namespace chatterino::commands {
|
||||||
|
|
||||||
|
QString setLoggingRules(const CommandContext &ctx);
|
||||||
|
|
||||||
|
QString toggleThemeReload(const CommandContext &ctx);
|
||||||
|
|
||||||
|
} // namespace chatterino::commands
|
112
src/controllers/commands/builtin/twitch/Shoutout.cpp
Normal file
112
src/controllers/commands/builtin/twitch/Shoutout.cpp
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#include "controllers/commands/builtin/twitch/Shoutout.hpp"
|
||||||
|
|
||||||
|
#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"
|
||||||
|
|
||||||
|
namespace chatterino::commands {
|
||||||
|
|
||||||
|
QString sendShoutout(const CommandContext &ctx)
|
||||||
|
{
|
||||||
|
auto *twitchChannel = ctx.twitchChannel;
|
||||||
|
auto channel = ctx.channel;
|
||||||
|
auto words = &ctx.words;
|
||||||
|
|
||||||
|
if (twitchChannel == nullptr)
|
||||||
|
{
|
||||||
|
channel->addMessage(makeSystemMessage(
|
||||||
|
"The /shoutout command only works in Twitch channels"));
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||||
|
if (currentUser->isAnon())
|
||||||
|
{
|
||||||
|
channel->addMessage(
|
||||||
|
makeSystemMessage("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"));
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto target = words->at(1);
|
||||||
|
|
||||||
|
using Error = HelixSendShoutoutError;
|
||||||
|
|
||||||
|
getHelix()->getUserByName(
|
||||||
|
target,
|
||||||
|
[twitchChannel, channel, currentUser, &target](const auto targetUser) {
|
||||||
|
getHelix()->sendShoutout(
|
||||||
|
twitchChannel->roomId(), targetUser.id,
|
||||||
|
currentUser->getUserId(),
|
||||||
|
[channel, targetUser]() {
|
||||||
|
channel->addMessage(makeSystemMessage(
|
||||||
|
QString("Sent shoutout to %1").arg(targetUser.login)));
|
||||||
|
},
|
||||||
|
[channel](auto error, auto message) {
|
||||||
|
QString errorMessage = "Failed to send shoutout - ";
|
||||||
|
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case Error::UserNotAuthorized: {
|
||||||
|
errorMessage += "You don't have permission to "
|
||||||
|
"perform that action.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::UserMissingScope: {
|
||||||
|
errorMessage += "Missing required scope. "
|
||||||
|
"Re-login with your "
|
||||||
|
"account and try again.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::Ratelimited: {
|
||||||
|
errorMessage +=
|
||||||
|
"You are being ratelimited by Twitch. "
|
||||||
|
"Try again in a few seconds.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::UserIsBroadcaster: {
|
||||||
|
errorMessage += "The broadcaster may not give "
|
||||||
|
"themselves a Shoutout.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::BroadcasterNotLive: {
|
||||||
|
errorMessage +=
|
||||||
|
"The broadcaster is not streaming live or "
|
||||||
|
"does not have one or more viewers.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Error::Unknown: {
|
||||||
|
errorMessage += message;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel->addMessage(makeSystemMessage(errorMessage));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[channel, target] {
|
||||||
|
// Equivalent error from IRC
|
||||||
|
channel->addMessage(
|
||||||
|
makeSystemMessage(QString("Invalid username: %1").arg(target)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino::commands
|
15
src/controllers/commands/builtin/twitch/Shoutout.hpp
Normal file
15
src/controllers/commands/builtin/twitch/Shoutout.hpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct CommandContext;
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
|
||||||
|
namespace chatterino::commands {
|
||||||
|
|
||||||
|
QString sendShoutout(const CommandContext &ctx);
|
||||||
|
|
||||||
|
} // namespace chatterino::commands
|
|
@ -2,6 +2,43 @@
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/// Loosely compares `lhs` with `rhs`.
|
||||||
|
/// This attempts to convert both variants to a common type if they're not equal.
|
||||||
|
bool looselyCompareVariants(QVariant &lhs, QVariant &rhs)
|
||||||
|
{
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
// Qt 6 and later don't convert types as much as Qt 5 did when comparing.
|
||||||
|
//
|
||||||
|
// Based on QVariant::cmp from Qt 5.15
|
||||||
|
// https://github.com/qt/qtbase/blob/29400a683f96867133b28299c0d0bd6bcf40df35/src/corelib/kernel/qvariant.cpp#L4039-L4071
|
||||||
|
if (lhs.metaType() != rhs.metaType())
|
||||||
|
{
|
||||||
|
if (rhs.canConvert(lhs.metaType()))
|
||||||
|
{
|
||||||
|
if (!rhs.convert(lhs.metaType()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// try the opposite conversion, it might work
|
||||||
|
qSwap(lhs, rhs);
|
||||||
|
if (!rhs.convert(lhs.metaType()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return lhs == rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino::filters {
|
namespace chatterino::filters {
|
||||||
|
|
||||||
BinaryOperation::BinaryOperation(TokenType op, ExpressionPtr left,
|
BinaryOperation::BinaryOperation(TokenType op, ExpressionPtr left,
|
||||||
|
@ -60,14 +97,14 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
return left.toString().compare(right.toString(),
|
return left.toString().compare(right.toString(),
|
||||||
Qt::CaseInsensitive) == 0;
|
Qt::CaseInsensitive) == 0;
|
||||||
}
|
}
|
||||||
return left == right;
|
return looselyCompareVariants(left, right);
|
||||||
case NEQ:
|
case NEQ:
|
||||||
if (variantTypesMatch(left, right, QMetaType::QString))
|
if (variantTypesMatch(left, right, QMetaType::QString))
|
||||||
{
|
{
|
||||||
return left.toString().compare(right.toString(),
|
return left.toString().compare(right.toString(),
|
||||||
Qt::CaseInsensitive) != 0;
|
Qt::CaseInsensitive) != 0;
|
||||||
}
|
}
|
||||||
return left != right;
|
return !looselyCompareVariants(left, right);
|
||||||
case LT:
|
case LT:
|
||||||
if (convertVariantTypes(left, right, QMetaType::Int))
|
if (convertVariantTypes(left, right, QMetaType::Int))
|
||||||
return left.toInt() < right.toInt();
|
return left.toInt() < right.toInt();
|
||||||
|
@ -92,13 +129,13 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
Qt::CaseInsensitive);
|
Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variantIs(left.type(), QMetaType::QVariantMap) &&
|
if (variantIs(left, QMetaType::QVariantMap) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
return left.toMap().contains(right.toString());
|
return left.toMap().contains(right.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variantIs(left.type(), QMetaType::QVariantList))
|
if (variantIs(left, QMetaType::QVariantList))
|
||||||
{
|
{
|
||||||
return left.toList().contains(right);
|
return left.toList().contains(right);
|
||||||
}
|
}
|
||||||
|
@ -112,7 +149,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
case STARTS_WITH:
|
case STARTS_WITH:
|
||||||
if (variantIs(left.type(), QMetaType::QStringList) &&
|
if (variantIs(left, QMetaType::QStringList) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
auto list = left.toStringList();
|
auto list = left.toStringList();
|
||||||
|
@ -121,7 +158,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
Qt::CaseInsensitive) == 0;
|
Qt::CaseInsensitive) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variantIs(left.type(), QMetaType::QVariantList))
|
if (variantIs(left, QMetaType::QVariantList))
|
||||||
{
|
{
|
||||||
return left.toList().startsWith(right);
|
return left.toList().startsWith(right);
|
||||||
}
|
}
|
||||||
|
@ -136,7 +173,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
case ENDS_WITH:
|
case ENDS_WITH:
|
||||||
if (variantIs(left.type(), QMetaType::QStringList) &&
|
if (variantIs(left, QMetaType::QStringList) &&
|
||||||
right.canConvert(QMetaType::QString))
|
right.canConvert(QMetaType::QString))
|
||||||
{
|
{
|
||||||
auto list = left.toStringList();
|
auto list = left.toStringList();
|
||||||
|
@ -145,7 +182,7 @@ QVariant BinaryOperation::execute(const ContextMap &context) const
|
||||||
Qt::CaseInsensitive) == 0;
|
Qt::CaseInsensitive) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variantIs(left.type(), QMetaType::QVariantList))
|
if (variantIs(left, QMetaType::QVariantList))
|
||||||
{
|
{
|
||||||
return left.toList().endsWith(right);
|
return left.toList().endsWith(right);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ QVariant ListExpression::execute(const ContextMap &context) const
|
||||||
for (const auto &exp : this->list_)
|
for (const auto &exp : this->list_)
|
||||||
{
|
{
|
||||||
auto res = exp->execute(context);
|
auto res = exp->execute(context);
|
||||||
if (allStrings && variantIsNot(res.type(), QMetaType::QString))
|
if (allStrings && variantIsNot(res, QMetaType::QString))
|
||||||
{
|
{
|
||||||
allStrings = false;
|
allStrings = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ void rebuildReplyThreadHighlight(Settings &settings,
|
||||||
const auto & /*senderName*/, const auto & /*originalMessage*/,
|
const auto & /*senderName*/, const auto & /*originalMessage*/,
|
||||||
const auto &flags,
|
const auto &flags,
|
||||||
const auto self) -> boost::optional<HighlightResult> {
|
const auto self) -> boost::optional<HighlightResult> {
|
||||||
if (flags.has(MessageFlag::ParticipatedThread) && !self)
|
if (flags.has(MessageFlag::SubscribedThread) && !self)
|
||||||
{
|
{
|
||||||
return HighlightResult{
|
return HighlightResult{
|
||||||
highlightAlert,
|
highlightAlert,
|
||||||
|
@ -186,7 +186,8 @@ void rebuildMessageHighlights(Settings &settings,
|
||||||
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
|
auto currentUser = getIApp()->getAccounts()->twitch.getCurrent();
|
||||||
QString currentUsername = currentUser->getUserName();
|
QString currentUsername = currentUser->getUserName();
|
||||||
|
|
||||||
if (settings.enableSelfHighlight && !currentUsername.isEmpty())
|
if (settings.enableSelfHighlight && !currentUsername.isEmpty() &&
|
||||||
|
!currentUser->isAnon())
|
||||||
{
|
{
|
||||||
HighlightPhrase highlight(
|
HighlightPhrase highlight(
|
||||||
currentUsername, settings.showSelfHighlightInMentions,
|
currentUsername, settings.showSelfHighlightInMentions,
|
||||||
|
|
|
@ -210,7 +210,7 @@ void HighlightModel::afterInit()
|
||||||
std::vector<QStandardItem *> threadMessageRow = this->createRow();
|
std::vector<QStandardItem *> threadMessageRow = this->createRow();
|
||||||
setBoolItem(threadMessageRow[Column::Pattern],
|
setBoolItem(threadMessageRow[Column::Pattern],
|
||||||
getSettings()->enableThreadHighlight.getValue(), true, false);
|
getSettings()->enableThreadHighlight.getValue(), true, false);
|
||||||
threadMessageRow[Column::Pattern]->setData("Participated Reply Threads",
|
threadMessageRow[Column::Pattern]->setData("Subscribed Reply Threads",
|
||||||
Qt::DisplayRole);
|
Qt::DisplayRole);
|
||||||
setBoolItem(threadMessageRow[Column::ShowInMentions],
|
setBoolItem(threadMessageRow[Column::ShowInMentions],
|
||||||
getSettings()->showThreadHighlightInMentions.getValue(), true,
|
getSettings()->showThreadHighlightInMentions.getValue(), true,
|
||||||
|
|
|
@ -5,6 +5,20 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
inline const std::vector<std::pair<QString, std::vector<QString>>>
|
||||||
|
HOTKEY_ARG_ON_OFF_TOGGLE = {
|
||||||
|
{"Toggle", {}},
|
||||||
|
{"Set to on", {"on"}},
|
||||||
|
{"Set to off", {"off"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const std::vector<std::pair<QString, std::vector<QString>>>
|
||||||
|
HOTKEY_ARG_WITH_OR_WITHOUT_SELECTION = {
|
||||||
|
{"No", {"withoutSelection"}},
|
||||||
|
{"Yes", {"withSelection"}},
|
||||||
|
};
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -13,6 +27,9 @@ struct ActionDefinition {
|
||||||
// displayName is the value that would be shown to a user when they edit or create a hotkey for an action
|
// displayName is the value that would be shown to a user when they edit or create a hotkey for an action
|
||||||
QString displayName;
|
QString displayName;
|
||||||
|
|
||||||
|
// argumentDescription is a description of the arguments in a format of
|
||||||
|
// "<required arg: description of possible values> [optional arg: possible
|
||||||
|
// values]"
|
||||||
QString argumentDescription = "";
|
QString argumentDescription = "";
|
||||||
|
|
||||||
// minCountArguments is the minimum amount of arguments the action accepts
|
// minCountArguments is the minimum amount of arguments the action accepts
|
||||||
|
@ -21,6 +38,20 @@ struct ActionDefinition {
|
||||||
|
|
||||||
// maxCountArguments is the maximum amount of arguments the action accepts
|
// maxCountArguments is the maximum amount of arguments the action accepts
|
||||||
uint8_t maxCountArguments = minCountArguments;
|
uint8_t maxCountArguments = minCountArguments;
|
||||||
|
|
||||||
|
// possibleArguments is empty or contains all possible argument values,
|
||||||
|
// it is an ordered mapping from option name (what the user sees) to
|
||||||
|
// arguments (what the action code will see).
|
||||||
|
// As std::map<K, V> does not guarantee order this is a std::vector<...>
|
||||||
|
std::vector<std::pair<QString, std::vector<QString>>> possibleArguments =
|
||||||
|
{};
|
||||||
|
|
||||||
|
// When possibleArguments are present this should be a string like
|
||||||
|
// "Direction:" which will be shown before the values from
|
||||||
|
// possibleArguments in the UI. Otherwise, it should be empty.
|
||||||
|
QString argumentsPrompt = "";
|
||||||
|
// A more detailed description of what argumentsPrompt means
|
||||||
|
QString argumentsPromptHover = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
using ActionDefinitionMap = std::map<QString, ActionDefinition>;
|
using ActionDefinitionMap = std::map<QString, ActionDefinition>;
|
||||||
|
@ -39,15 +70,22 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
}},
|
}},
|
||||||
{"scrollPage",
|
{"scrollPage",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Scroll",
|
.displayName = "Scroll",
|
||||||
"<up or down>",
|
.argumentDescription = "<direction: up or down>",
|
||||||
1,
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Up", {"up"}},
|
||||||
|
{"Down", {"down"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Direction:",
|
||||||
}},
|
}},
|
||||||
{"search", ActionDefinition{"Focus search box"}},
|
{"search", ActionDefinition{"Focus search box"}},
|
||||||
{"execModeratorAction",
|
{"execModeratorAction",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Usercard: execute moderation action",
|
"Usercard: execute moderation action",
|
||||||
"<ban, unban or number of the timeout button to use>", 1}},
|
"<ban, unban or number of the timeout button to use>", 1}},
|
||||||
|
{"pin", ActionDefinition{"Usercard, reply thread: pin window"}},
|
||||||
}},
|
}},
|
||||||
{HotkeyCategory::Split,
|
{HotkeyCategory::Split,
|
||||||
{
|
{
|
||||||
|
@ -57,24 +95,42 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"delete", ActionDefinition{"Close"}},
|
{"delete", ActionDefinition{"Close"}},
|
||||||
{"focus",
|
{"focus",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Focus neighbouring split",
|
.displayName = "Focus neighbouring split",
|
||||||
"<up, down, left, or right>",
|
.argumentDescription = "<direction: up, down, left or right>",
|
||||||
1,
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Up", {"up"}},
|
||||||
|
{"Down", {"down"}},
|
||||||
|
{"Left", {"left"}},
|
||||||
|
{"Right", {"right"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Direction:",
|
||||||
|
.argumentsPromptHover =
|
||||||
|
"Which direction to look for a split to focus?",
|
||||||
}},
|
}},
|
||||||
{"openInBrowser", ActionDefinition{"Open channel in browser"}},
|
{"openInBrowser", ActionDefinition{"Open channel in browser"}},
|
||||||
{"openInCustomPlayer",
|
{"openInCustomPlayer",
|
||||||
ActionDefinition{"Open stream in custom player"}},
|
ActionDefinition{"Open stream in custom player"}},
|
||||||
{"openInStreamlink", ActionDefinition{"Open stream in streamlink"}},
|
{"openInStreamlink", ActionDefinition{"Open stream in streamlink"}},
|
||||||
{"openModView", ActionDefinition{"Open mod view in browser"}},
|
{"openModView", ActionDefinition{"Open mod view in browser"}},
|
||||||
{"openViewerList", ActionDefinition{"Open viewer list"}},
|
{"openViewerList", ActionDefinition{"Open chatter list"}},
|
||||||
{"pickFilters", ActionDefinition{"Pick filters"}},
|
{"pickFilters", ActionDefinition{"Pick filters"}},
|
||||||
{"reconnect", ActionDefinition{"Reconnect to chat"}},
|
{"reconnect", ActionDefinition{"Reconnect to chat"}},
|
||||||
{"reloadEmotes",
|
{"reloadEmotes",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Reload emotes",
|
.displayName = "Reload emotes",
|
||||||
"[channel or subscriber]",
|
.argumentDescription =
|
||||||
0,
|
"[type: channel or subscriber; default: all emotes]",
|
||||||
1,
|
.minCountArguments = 0,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"All emotes", {}},
|
||||||
|
{"Channel emotes only", {"channel"}},
|
||||||
|
{"Subscriber emotes only", {"subscriber"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Emote type:",
|
||||||
|
.argumentsPromptHover = "Which emotes should Chatterino reload",
|
||||||
}},
|
}},
|
||||||
{"runCommand",
|
{"runCommand",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
|
@ -84,25 +140,41 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
}},
|
}},
|
||||||
{"scrollPage",
|
{"scrollPage",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Scroll",
|
.displayName = "Scroll",
|
||||||
"<up or down>",
|
.argumentDescription = "<up or down>",
|
||||||
1,
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Up", {"up"}},
|
||||||
|
{"Down", {"down"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Direction:",
|
||||||
|
.argumentsPromptHover =
|
||||||
|
"Which direction do you want to see more messages",
|
||||||
}},
|
}},
|
||||||
{"scrollToBottom", ActionDefinition{"Scroll to the bottom"}},
|
{"scrollToBottom", ActionDefinition{"Scroll to the bottom"}},
|
||||||
{"scrollToTop", ActionDefinition{"Scroll to the top"}},
|
{"scrollToTop", ActionDefinition{"Scroll to the top"}},
|
||||||
{"setChannelNotification",
|
{"setChannelNotification",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Set channel live notification",
|
.displayName = "Set channel live notification",
|
||||||
"[on or off. default: toggle]",
|
.argumentDescription = "[on or off. default: toggle]",
|
||||||
0,
|
.minCountArguments = 0,
|
||||||
1,
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments = HOTKEY_ARG_ON_OFF_TOGGLE,
|
||||||
|
.argumentsPrompt = "New value:",
|
||||||
|
.argumentsPromptHover = "Should the channel live notification be "
|
||||||
|
"enabled, disabled or toggled",
|
||||||
}},
|
}},
|
||||||
{"setModerationMode",
|
{"setModerationMode",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Set moderation mode",
|
.displayName = "Set moderation mode",
|
||||||
"[on or off. default: toggle]",
|
.argumentDescription = "[on or off. default: toggle]",
|
||||||
0,
|
.minCountArguments = 0,
|
||||||
1,
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments = HOTKEY_ARG_ON_OFF_TOGGLE,
|
||||||
|
.argumentsPrompt = "New value:",
|
||||||
|
.argumentsPromptHover =
|
||||||
|
"Should the moderation mode be enabled, disabled or toggled",
|
||||||
}},
|
}},
|
||||||
{"showSearch", ActionDefinition{"Search current channel"}},
|
{"showSearch", ActionDefinition{"Search current channel"}},
|
||||||
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
||||||
|
@ -114,21 +186,38 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"clear", ActionDefinition{"Clear message"}},
|
{"clear", ActionDefinition{"Clear message"}},
|
||||||
{"copy",
|
{"copy",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Copy",
|
.displayName = "Copy",
|
||||||
"<source of text: split, splitInput or auto>",
|
.argumentDescription =
|
||||||
1,
|
"<source of text: auto, split or splitInput>",
|
||||||
|
.minCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Automatic", {"auto"}},
|
||||||
|
{"Split", {"split"}},
|
||||||
|
{"Split Input", {"splitInput"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Source of text:",
|
||||||
}},
|
}},
|
||||||
{"cursorToStart",
|
{"cursorToStart",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"To start of message",
|
.displayName = "To start of message",
|
||||||
"<withSelection or withoutSelection>",
|
.argumentDescription =
|
||||||
1,
|
"<selection mode: withSelection or withoutSelection>",
|
||||||
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments = HOTKEY_ARG_WITH_OR_WITHOUT_SELECTION,
|
||||||
|
.argumentsPrompt = "Select text from cursor to start:",
|
||||||
|
// XXX: write a hover for this that doesn't suck
|
||||||
}},
|
}},
|
||||||
{"cursorToEnd",
|
{"cursorToEnd",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"To end of message",
|
.displayName = "To end of message",
|
||||||
"<withSelection or withoutSelection>",
|
.argumentDescription =
|
||||||
1,
|
"<selection mode: withSelection or withoutSelection>",
|
||||||
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments = HOTKEY_ARG_WITH_OR_WITHOUT_SELECTION,
|
||||||
|
.argumentsPrompt = "Select text from cursor to end:",
|
||||||
|
// XXX: write a hover for this that doesn't suck
|
||||||
}},
|
}},
|
||||||
{"nextMessage", ActionDefinition{"Choose next sent message"}},
|
{"nextMessage", ActionDefinition{"Choose next sent message"}},
|
||||||
{"openEmotesPopup", ActionDefinition{"Open emotes list"}},
|
{"openEmotesPopup", ActionDefinition{"Open emotes list"}},
|
||||||
|
@ -140,10 +229,16 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"selectWord", ActionDefinition{"Select word"}},
|
{"selectWord", ActionDefinition{"Select word"}},
|
||||||
{"sendMessage",
|
{"sendMessage",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Send message",
|
.displayName = "Send message",
|
||||||
"[keepInput to not clear the text after sending]",
|
.argumentDescription =
|
||||||
0,
|
"[keepInput to not clear the text after sending]",
|
||||||
1,
|
.minCountArguments = 0,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Default behavior", {}},
|
||||||
|
{"Keep message in input after sending it", {"keepInput"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Behavior:",
|
||||||
}},
|
}},
|
||||||
{"undo", ActionDefinition{"Undo"}},
|
{"undo", ActionDefinition{"Undo"}},
|
||||||
|
|
||||||
|
@ -163,7 +258,7 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"moveTab",
|
{"moveTab",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Move tab",
|
"Move tab",
|
||||||
"<next, previous, or new index of tab>",
|
"<where to move the tab: next, previous, or new index of tab>",
|
||||||
1,
|
1,
|
||||||
}},
|
}},
|
||||||
{"newSplit", ActionDefinition{"Create a new split"}},
|
{"newSplit", ActionDefinition{"Create a new split"}},
|
||||||
|
@ -172,40 +267,78 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"openTab",
|
{"openTab",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Select tab",
|
"Select tab",
|
||||||
"<last, next, previous, or index of tab to select>",
|
"<which tab to select: last, next, previous, or index>",
|
||||||
1,
|
1,
|
||||||
}},
|
}},
|
||||||
{"openQuickSwitcher", ActionDefinition{"Open the quick switcher"}},
|
{"openQuickSwitcher", ActionDefinition{"Open the quick switcher"}},
|
||||||
{"popup",
|
{"popup",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"New popup",
|
.displayName = "New popup",
|
||||||
"<split or window>",
|
.argumentDescription = "<split or window>",
|
||||||
1,
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{
|
||||||
|
{"Focused Split", {"split"}},
|
||||||
|
{"Entire Tab", {"window"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Include:",
|
||||||
|
.argumentsPromptHover =
|
||||||
|
"What should be included in the new popup",
|
||||||
}},
|
}},
|
||||||
{"quit", ActionDefinition{"Quit Chatterino"}},
|
{"quit", ActionDefinition{"Quit Chatterino"}},
|
||||||
{"removeTab", ActionDefinition{"Remove current tab"}},
|
{"removeTab", ActionDefinition{"Remove current tab"}},
|
||||||
{"reopenSplit", ActionDefinition{"Reopen closed split"}},
|
{"reopenSplit", ActionDefinition{"Reopen closed split"}},
|
||||||
{"setStreamerMode",
|
{"setStreamerMode",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Set streamer mode",
|
.displayName = "Set streamer mode",
|
||||||
"[on, off, toggle, or auto. default: toggle]",
|
.argumentDescription =
|
||||||
0,
|
"[on, off, toggle, or auto. default: toggle]",
|
||||||
1,
|
.minCountArguments = 0,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments =
|
||||||
|
{
|
||||||
|
{"Toggle on/off", {}},
|
||||||
|
{"Set to on", {"on"}},
|
||||||
|
{"Set to off", {"off"}},
|
||||||
|
{"Set to automatic", {"auto"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "New value:",
|
||||||
|
.argumentsPromptHover =
|
||||||
|
"Should streamer mode be enabled, disabled, toggled (on/off) "
|
||||||
|
"or set to auto",
|
||||||
}},
|
}},
|
||||||
{"toggleLocalR9K", ActionDefinition{"Toggle local R9K"}},
|
{"toggleLocalR9K", ActionDefinition{"Toggle local R9K"}},
|
||||||
{"zoom",
|
{"zoom",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Zoom in/out",
|
.displayName = "Zoom in/out",
|
||||||
"<in, out, or reset>",
|
.argumentDescription = "Argument:",
|
||||||
1,
|
.minCountArguments = 1,
|
||||||
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments =
|
||||||
|
{
|
||||||
|
{"Zoom in", {"in"}},
|
||||||
|
{"Zoom out", {"out"}},
|
||||||
|
{"Reset zoom", {"reset"}},
|
||||||
|
},
|
||||||
|
.argumentsPrompt = "Option:",
|
||||||
}},
|
}},
|
||||||
{"setTabVisibility",
|
{"setTabVisibility",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Set tab visibility",
|
.displayName = "Set tab visibility",
|
||||||
"[on, off, or toggle. default: toggle]",
|
.argumentDescription = "[on, off, toggle, liveOnly, or "
|
||||||
0,
|
"toggleLiveOnly. default: toggle]",
|
||||||
1,
|
.minCountArguments = 0,
|
||||||
}}}},
|
.maxCountArguments = 1,
|
||||||
|
.possibleArguments{{"Toggle", {}},
|
||||||
|
{"Set to on", {"on"}},
|
||||||
|
{"Set to off", {"off"}},
|
||||||
|
{"Live only on", {"liveOnly"}},
|
||||||
|
{"Live only toggle", {"toggleLiveOnly"}}},
|
||||||
|
.argumentsPrompt = "New value:",
|
||||||
|
.argumentsPromptHover = "Should the tabs be enabled, disabled, "
|
||||||
|
"toggled, or live-only.",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -500,6 +500,10 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
|
||||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Window,
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Window,
|
||||||
QKeySequence("Ctrl+U"), "setTabVisibility",
|
QKeySequence("Ctrl+U"), "setTabVisibility",
|
||||||
{"toggle"}, "toggle tab visibility");
|
{"toggle"}, "toggle tab visibility");
|
||||||
|
|
||||||
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Window,
|
||||||
|
QKeySequence("Ctrl+Shift+L"), "setTabVisibility",
|
||||||
|
{"toggleLiveOnly"}, "toggle live tabs only");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "controllers/hotkeys/HotkeyHelpers.hpp"
|
#include "controllers/hotkeys/HotkeyHelpers.hpp"
|
||||||
|
|
||||||
|
#include "controllers/hotkeys/ActionNames.hpp"
|
||||||
|
#include "controllers/hotkeys/HotkeyCategory.hpp"
|
||||||
|
|
||||||
|
#include <boost/optional/optional.hpp>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -27,4 +31,20 @@ std::vector<QString> parseHotkeyArguments(QString argumentString)
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::optional<ActionDefinition> findHotkeyActionDefinition(
|
||||||
|
HotkeyCategory category, const QString &action)
|
||||||
|
{
|
||||||
|
auto allActions = actionNames.find(category);
|
||||||
|
if (allActions != actionNames.end())
|
||||||
|
{
|
||||||
|
const auto &actionsMap = allActions->second;
|
||||||
|
auto definition = actionsMap.find(action);
|
||||||
|
if (definition != actionsMap.end())
|
||||||
|
{
|
||||||
|
return {definition->second};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "controllers/hotkeys/ActionNames.hpp"
|
||||||
|
|
||||||
|
#include <boost/optional/optional.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -7,5 +10,7 @@
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
std::vector<QString> parseHotkeyArguments(QString argumentString);
|
std::vector<QString> parseHotkeyArguments(QString argumentString);
|
||||||
|
boost::optional<ActionDefinition> findHotkeyActionDefinition(
|
||||||
|
HotkeyCategory category, const QString &action);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -32,10 +32,10 @@ bool isIgnoredMessage(IgnoredMessageParameters &¶ms)
|
||||||
{
|
{
|
||||||
auto sourceUserID = params.twitchUserID;
|
auto sourceUserID = params.twitchUserID;
|
||||||
|
|
||||||
auto blocks =
|
bool isBlocked =
|
||||||
getApp()->accounts->twitch.getCurrent()->accessBlockedUserIds();
|
getApp()->accounts->twitch.getCurrent()->blockedUserIds().contains(
|
||||||
|
sourceUserID);
|
||||||
if (auto it = blocks->find(sourceUserID); it != blocks->end())
|
if (isBlocked)
|
||||||
{
|
{
|
||||||
switch (static_cast<ShowIgnoredUsersMessages>(
|
switch (static_cast<ShowIgnoredUsersMessages>(
|
||||||
getSettings()->showBlockedUsersMessages.getValue()))
|
getSettings()->showBlockedUsersMessages.getValue()))
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
#include "util/RapidJsonSerializeQString.hpp"
|
#include "util/RapidJsonSerializeQString.hpp"
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include <pajlada/serialize.hpp>
|
#include <pajlada/serialize.hpp>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -58,25 +59,25 @@ public:
|
||||||
return this->isCaseSensitive_;
|
return this->isCaseSensitive_;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool match(QString &usernameText) const
|
[[nodiscard]] boost::optional<QString> match(
|
||||||
|
const QString &usernameText) const
|
||||||
{
|
{
|
||||||
if (this->isRegex())
|
if (this->isRegex())
|
||||||
{
|
{
|
||||||
if (!this->regex_.isValid())
|
if (!this->regex_.isValid())
|
||||||
{
|
{
|
||||||
return false;
|
return boost::none;
|
||||||
}
|
}
|
||||||
if (this->name().isEmpty())
|
if (this->name().isEmpty())
|
||||||
{
|
{
|
||||||
return false;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto workingCopy = usernameText;
|
auto workingCopy = usernameText;
|
||||||
workingCopy.replace(this->regex_, this->replace());
|
workingCopy.replace(this->regex_, this->replace());
|
||||||
if (workingCopy != usernameText)
|
if (workingCopy != usernameText)
|
||||||
{
|
{
|
||||||
usernameText = workingCopy;
|
return workingCopy;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -85,12 +86,11 @@ public:
|
||||||
this->name().compare(usernameText, this->caseSensitivity());
|
this->name().compare(usernameText, this->caseSensitivity());
|
||||||
if (res == 0)
|
if (res == 0)
|
||||||
{
|
{
|
||||||
usernameText = this->replace();
|
return this->replace();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -20,7 +20,6 @@ constexpr const auto NUM_SOUNDS = 4;
|
||||||
SoundController::SoundController()
|
SoundController::SoundController()
|
||||||
: context(std::make_unique<ma_context>())
|
: context(std::make_unique<ma_context>())
|
||||||
, resourceManager(std::make_unique<ma_resource_manager>())
|
, resourceManager(std::make_unique<ma_resource_manager>())
|
||||||
, device(std::make_unique<ma_device>())
|
|
||||||
, engine(std::make_unique<ma_engine>())
|
, engine(std::make_unique<ma_engine>())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -66,27 +65,9 @@ void SoundController::initialize(Settings &settings, Paths &paths)
|
||||||
this->defaultPingData = defaultPingFile.readAll();
|
this->defaultPingData = defaultPingFile.readAll();
|
||||||
|
|
||||||
/// Initialize a sound device
|
/// Initialize a sound device
|
||||||
auto deviceConfig = ma_device_config_init(ma_device_type_playback);
|
if (!this->recreateDevice())
|
||||||
deviceConfig.playback.pDeviceID = nullptr;
|
|
||||||
deviceConfig.playback.format = this->resourceManager->config.decodedFormat;
|
|
||||||
deviceConfig.playback.channels = 0;
|
|
||||||
deviceConfig.pulse.pStreamNamePlayback = "Chatterino MA";
|
|
||||||
deviceConfig.sampleRate = this->resourceManager->config.decodedSampleRate;
|
|
||||||
deviceConfig.dataCallback = ma_engine_data_callback_internal;
|
|
||||||
deviceConfig.pUserData = this->engine.get();
|
|
||||||
|
|
||||||
result =
|
|
||||||
ma_device_init(this->context.get(), &deviceConfig, this->device.get());
|
|
||||||
if (result != MA_SUCCESS)
|
|
||||||
{
|
{
|
||||||
qCWarning(chatterinoSound) << "Error initializing device:" << result;
|
qCWarning(chatterinoSound) << "Failed to create the initial device";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = ma_device_start(this->device.get());
|
|
||||||
if (result != MA_SUCCESS)
|
|
||||||
{
|
|
||||||
qCWarning(chatterinoSound) << "Error starting device:" << result;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +153,11 @@ SoundController::~SoundController()
|
||||||
}
|
}
|
||||||
|
|
||||||
ma_engine_uninit(this->engine.get());
|
ma_engine_uninit(this->engine.get());
|
||||||
ma_device_uninit(this->device.get());
|
if (this->device)
|
||||||
|
{
|
||||||
|
ma_device_uninit(this->device.get());
|
||||||
|
this->device.reset();
|
||||||
|
}
|
||||||
ma_resource_manager_uninit(this->resourceManager.get());
|
ma_resource_manager_uninit(this->resourceManager.get());
|
||||||
ma_context_uninit(this->context.get());
|
ma_context_uninit(this->context.get());
|
||||||
}
|
}
|
||||||
|
@ -204,7 +189,12 @@ void SoundController::play(const QUrl &sound)
|
||||||
{
|
{
|
||||||
qCWarning(chatterinoSound)
|
qCWarning(chatterinoSound)
|
||||||
<< "Failed to start the sound device" << result;
|
<< "Failed to start the sound device" << result;
|
||||||
return;
|
|
||||||
|
if (!this->recreateDevice())
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoSound) << "Failed to recreate device";
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qCInfo(chatterinoSound) << "Successfully restarted the sound device";
|
qCInfo(chatterinoSound) << "Successfully restarted the sound device";
|
||||||
|
@ -234,4 +224,44 @@ void SoundController::play(const QUrl &sound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SoundController::recreateDevice()
|
||||||
|
{
|
||||||
|
ma_result result{};
|
||||||
|
|
||||||
|
if (this->device)
|
||||||
|
{
|
||||||
|
// Release the previous device first
|
||||||
|
qCDebug(chatterinoSound) << "Uniniting previously created device";
|
||||||
|
ma_device_uninit(this->device.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
this->device = std::make_unique<ma_device>();
|
||||||
|
|
||||||
|
auto deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||||
|
deviceConfig.playback.pDeviceID = nullptr;
|
||||||
|
deviceConfig.playback.format = this->resourceManager->config.decodedFormat;
|
||||||
|
deviceConfig.playback.channels = 0;
|
||||||
|
deviceConfig.pulse.pStreamNamePlayback = "Chatterino MA";
|
||||||
|
deviceConfig.sampleRate = this->resourceManager->config.decodedSampleRate;
|
||||||
|
deviceConfig.dataCallback = ma_engine_data_callback_internal;
|
||||||
|
deviceConfig.pUserData = this->engine.get();
|
||||||
|
|
||||||
|
result =
|
||||||
|
ma_device_init(this->context.get(), &deviceConfig, this->device.get());
|
||||||
|
if (result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoSound) << "Error initializing device:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ma_device_start(this->device.get());
|
||||||
|
if (result != MA_SUCCESS)
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoSound) << "Error starting device:" << result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -45,7 +45,7 @@ private:
|
||||||
// Used for storing & reusing sounds to be played
|
// Used for storing & reusing sounds to be played
|
||||||
std::unique_ptr<ma_resource_manager> resourceManager;
|
std::unique_ptr<ma_resource_manager> resourceManager;
|
||||||
// The sound device we're playing sound into
|
// The sound device we're playing sound into
|
||||||
std::unique_ptr<ma_device> device;
|
std::unique_ptr<ma_device> device{nullptr};
|
||||||
// The engine is a high-level API for playing sounds from paths in a simple & efficient-enough manner
|
// The engine is a high-level API for playing sounds from paths in a simple & efficient-enough manner
|
||||||
std::unique_ptr<ma_engine> engine;
|
std::unique_ptr<ma_engine> engine;
|
||||||
|
|
||||||
|
@ -64,6 +64,13 @@ private:
|
||||||
|
|
||||||
bool initialized{false};
|
bool initialized{false};
|
||||||
|
|
||||||
|
// Recreates the sound device
|
||||||
|
// This is used during initialization, and can also be used if the device
|
||||||
|
// needs to be recreated during playback
|
||||||
|
//
|
||||||
|
// Returns false on failure
|
||||||
|
bool recreateDevice();
|
||||||
|
|
||||||
friend class Application;
|
friend class Application;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
190
src/controllers/twitch/LiveController.cpp
Normal file
190
src/controllers/twitch/LiveController.cpp
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
#include "controllers/twitch/LiveController.hpp"
|
||||||
|
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
#include "util/Helpers.hpp"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
const auto &LOG = chatterinoTwitchLiveController;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
TwitchLiveController::TwitchLiveController()
|
||||||
|
{
|
||||||
|
QObject::connect(&this->refreshTimer, &QTimer::timeout, [this] {
|
||||||
|
this->request();
|
||||||
|
});
|
||||||
|
this->refreshTimer.start(TwitchLiveController::REFRESH_INTERVAL);
|
||||||
|
|
||||||
|
QObject::connect(&this->immediateRequestTimer, &QTimer::timeout, [this] {
|
||||||
|
QStringList channelIDs;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock immediateRequestsLock(
|
||||||
|
this->immediateRequestsMutex);
|
||||||
|
for (const auto &channelID : this->immediateRequests)
|
||||||
|
{
|
||||||
|
channelIDs.append(channelID);
|
||||||
|
}
|
||||||
|
this->immediateRequests.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelIDs.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->request(channelIDs);
|
||||||
|
});
|
||||||
|
this->immediateRequestTimer.start(
|
||||||
|
TwitchLiveController::IMMEDIATE_REQUEST_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchLiveController::add(const std::shared_ptr<TwitchChannel> &newChannel)
|
||||||
|
{
|
||||||
|
assert(newChannel != nullptr);
|
||||||
|
|
||||||
|
const auto channelID = newChannel->roomId();
|
||||||
|
assert(!channelID.isEmpty());
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock lock(this->channelsMutex);
|
||||||
|
this->channels[channelID] = newChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::unique_lock immediateRequestsLock(this->immediateRequestsMutex);
|
||||||
|
this->immediateRequests.emplace(channelID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchLiveController::request(std::optional<QStringList> optChannelIDs)
|
||||||
|
{
|
||||||
|
QStringList channelIDs;
|
||||||
|
|
||||||
|
if (optChannelIDs)
|
||||||
|
{
|
||||||
|
channelIDs = *optChannelIDs;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->channelsMutex);
|
||||||
|
|
||||||
|
for (const auto &channelList : this->channels)
|
||||||
|
{
|
||||||
|
channelIDs.append(channelList.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelIDs.isEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto batches =
|
||||||
|
splitListIntoBatches(channelIDs, TwitchLiveController::BATCH_SIZE);
|
||||||
|
|
||||||
|
qCDebug(LOG) << "Make" << batches.size() << "requests";
|
||||||
|
|
||||||
|
for (const auto &batch : batches)
|
||||||
|
{
|
||||||
|
// TODO: Explore making this concurrent
|
||||||
|
getHelix()->fetchStreams(
|
||||||
|
batch, {},
|
||||||
|
[this, batch{batch}](const auto &streams) {
|
||||||
|
std::unordered_map<QString, std::optional<HelixStream>> results;
|
||||||
|
|
||||||
|
for (const auto &channelID : batch)
|
||||||
|
{
|
||||||
|
results[channelID] = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &stream : streams)
|
||||||
|
{
|
||||||
|
results[stream.userId] = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList deadChannels;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->channelsMutex);
|
||||||
|
for (const auto &result : results)
|
||||||
|
{
|
||||||
|
auto it = this->channels.find(result.first);
|
||||||
|
if (it != channels.end())
|
||||||
|
{
|
||||||
|
if (auto channel = it->second.lock(); channel)
|
||||||
|
{
|
||||||
|
channel->updateStreamStatus(result.second);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deadChannels.append(result.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deadChannels.isEmpty())
|
||||||
|
{
|
||||||
|
std::unique_lock lock(this->channelsMutex);
|
||||||
|
for (const auto &deadChannel : deadChannels)
|
||||||
|
{
|
||||||
|
this->channels.erase(deadChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[] {
|
||||||
|
qCWarning(LOG) << "Failed stream check request";
|
||||||
|
},
|
||||||
|
[] {});
|
||||||
|
|
||||||
|
// TODO: Explore making this concurrent
|
||||||
|
getHelix()->fetchChannels(
|
||||||
|
batch,
|
||||||
|
[this, batch{batch}](const auto &helixChannels) {
|
||||||
|
QStringList deadChannels;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(this->channelsMutex);
|
||||||
|
for (const auto &helixChannel : helixChannels)
|
||||||
|
{
|
||||||
|
auto it = this->channels.find(helixChannel.userId);
|
||||||
|
if (it != this->channels.end())
|
||||||
|
{
|
||||||
|
if (auto channel = it->second.lock(); channel)
|
||||||
|
{
|
||||||
|
channel->updateStreamTitle(helixChannel.title);
|
||||||
|
channel->updateDisplayName(helixChannel.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deadChannels.append(helixChannel.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deadChannels.isEmpty())
|
||||||
|
{
|
||||||
|
std::unique_lock lock(this->channelsMutex);
|
||||||
|
for (const auto &deadChannel : deadChannels)
|
||||||
|
{
|
||||||
|
this->channels.erase(deadChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[] {
|
||||||
|
qCWarning(LOG) << "Failed stream check request";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
89
src/controllers/twitch/LiveController.hpp
Normal file
89
src/controllers/twitch/LiveController.hpp
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/Singleton.hpp"
|
||||||
|
#include "util/QStringHash.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class TwitchChannel;
|
||||||
|
|
||||||
|
class ITwitchLiveController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ITwitchLiveController() = default;
|
||||||
|
|
||||||
|
virtual void add(const std::shared_ptr<TwitchChannel> &newChannel) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TwitchLiveController : public ITwitchLiveController, public Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Controls how often all channels have their stream status refreshed
|
||||||
|
static constexpr std::chrono::seconds REFRESH_INTERVAL{30};
|
||||||
|
|
||||||
|
// Controls how quickly new channels have their stream status loaded
|
||||||
|
static constexpr std::chrono::seconds IMMEDIATE_REQUEST_INTERVAL{1};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many channels to include in a single request
|
||||||
|
*
|
||||||
|
* Should not be more than 100
|
||||||
|
**/
|
||||||
|
static constexpr int BATCH_SIZE{100};
|
||||||
|
|
||||||
|
TwitchLiveController();
|
||||||
|
|
||||||
|
// Add a Twitch channel to be queried for live status
|
||||||
|
// A request is made within a few seconds if this is the first time this channel is added
|
||||||
|
void add(const std::shared_ptr<TwitchChannel> &newChannel) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Run batched Helix Channels & Stream requests for channels
|
||||||
|
*
|
||||||
|
* If a list of channel IDs is passed to request, we only make a request for those channels
|
||||||
|
*
|
||||||
|
* If no list of channels is passed to request (the default behaviour), we make requests for all channels
|
||||||
|
* in the `channels` map.
|
||||||
|
**/
|
||||||
|
void request(std::optional<QStringList> optChannelIDs = std::nullopt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of channel IDs pointing to their Twitch Channel
|
||||||
|
*
|
||||||
|
* These channels will have their stream status updated every REFRESH_INTERVAL seconds
|
||||||
|
**/
|
||||||
|
std::unordered_map<QString, std::weak_ptr<TwitchChannel>> channels;
|
||||||
|
std::shared_mutex channelsMutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of channels that need an immediate live status update
|
||||||
|
*
|
||||||
|
* These channels will have their stream status updated after at most IMMEDIATE_REQUEST_INTERVAL seconds
|
||||||
|
**/
|
||||||
|
std::unordered_set<QString> immediateRequests;
|
||||||
|
std::mutex immediateRequestsMutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer responsible for refreshing `channels`
|
||||||
|
**/
|
||||||
|
QTimer refreshTimer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer responsible for refreshing `immediateRequests`
|
||||||
|
**/
|
||||||
|
QTimer immediateRequestTimer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -76,6 +76,8 @@ namespace detail {
|
||||||
60000);
|
60000);
|
||||||
}
|
}
|
||||||
this->processOffset();
|
this->processOffset();
|
||||||
|
DebugCount::increase("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever loaded)", this->memoryUsage());
|
||||||
}
|
}
|
||||||
|
|
||||||
Frames::~Frames()
|
Frames::~Frames()
|
||||||
|
@ -91,10 +93,27 @@ namespace detail {
|
||||||
{
|
{
|
||||||
DebugCount::decrease("animated images");
|
DebugCount::decrease("animated images");
|
||||||
}
|
}
|
||||||
|
DebugCount::decrease("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever unloaded)",
|
||||||
|
this->memoryUsage());
|
||||||
|
|
||||||
this->gifTimerConnection_.disconnect();
|
this->gifTimerConnection_.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t Frames::memoryUsage() const
|
||||||
|
{
|
||||||
|
int64_t usage = 0;
|
||||||
|
for (const auto &frame : this->items_)
|
||||||
|
{
|
||||||
|
auto sz = frame.image.size();
|
||||||
|
auto area = sz.width() * sz.height();
|
||||||
|
auto memory = area * frame.image.depth();
|
||||||
|
|
||||||
|
usage += memory;
|
||||||
|
}
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
void Frames::advance()
|
void Frames::advance()
|
||||||
{
|
{
|
||||||
this->durationOffset_ += GIF_FRAME_LENGTH;
|
this->durationOffset_ += GIF_FRAME_LENGTH;
|
||||||
|
@ -131,6 +150,9 @@ namespace detail {
|
||||||
{
|
{
|
||||||
DebugCount::decrease("loaded images");
|
DebugCount::decrease("loaded images");
|
||||||
}
|
}
|
||||||
|
DebugCount::decrease("image bytes", this->memoryUsage());
|
||||||
|
DebugCount::increase("image bytes (ever unloaded)",
|
||||||
|
this->memoryUsage());
|
||||||
|
|
||||||
this->items_.clear();
|
this->items_.clear();
|
||||||
this->index_ = 0;
|
this->index_ = 0;
|
||||||
|
@ -573,8 +595,8 @@ ImageExpirationPool::ImageExpirationPool()
|
||||||
|
|
||||||
ImageExpirationPool &ImageExpirationPool::instance()
|
ImageExpirationPool &ImageExpirationPool::instance()
|
||||||
{
|
{
|
||||||
static ImageExpirationPool instance;
|
static auto *instance = new ImageExpirationPool;
|
||||||
return instance;
|
return *instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageExpirationPool::addImagePtr(ImagePtr imgPtr)
|
void ImageExpirationPool::addImagePtr(ImagePtr imgPtr)
|
||||||
|
@ -589,14 +611,26 @@ void ImageExpirationPool::removeImagePtr(Image *rawPtr)
|
||||||
this->allImages_.erase(rawPtr);
|
this->allImages_.erase(rawPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageExpirationPool::freeAll()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
||||||
|
{
|
||||||
|
auto img = it->second.lock();
|
||||||
|
img->expireFrames();
|
||||||
|
it = this->allImages_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->freeOld();
|
||||||
|
}
|
||||||
|
|
||||||
void ImageExpirationPool::freeOld()
|
void ImageExpirationPool::freeOld()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
std::lock_guard<std::mutex> lock(this->mutex_);
|
||||||
|
|
||||||
# ifndef NDEBUG
|
|
||||||
size_t numExpired = 0;
|
size_t numExpired = 0;
|
||||||
size_t eligible = 0;
|
size_t eligible = 0;
|
||||||
# endif
|
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
for (auto it = this->allImages_.begin(); it != this->allImages_.end();)
|
||||||
|
@ -617,17 +651,13 @@ void ImageExpirationPool::freeOld()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ifndef NDEBUG
|
|
||||||
++eligible;
|
++eligible;
|
||||||
# endif
|
|
||||||
|
|
||||||
// Check if image has expired and, if so, expire its frame data
|
// Check if image has expired and, if so, expire its frame data
|
||||||
auto diff = now - img->lastUsed_;
|
auto diff = now - img->lastUsed_;
|
||||||
if (diff > IMAGE_POOL_IMAGE_LIFETIME)
|
if (diff > IMAGE_POOL_IMAGE_LIFETIME)
|
||||||
{
|
{
|
||||||
# ifndef NDEBUG
|
|
||||||
++numExpired;
|
++numExpired;
|
||||||
# endif
|
|
||||||
img->expireFrames();
|
img->expireFrames();
|
||||||
// erase without mutex locking issue
|
// erase without mutex locking issue
|
||||||
it = this->allImages_.erase(it);
|
it = this->allImages_.erase(it);
|
||||||
|
@ -641,6 +671,9 @@ void ImageExpirationPool::freeOld()
|
||||||
qCDebug(chatterinoImage) << "freed frame data for" << numExpired << "/"
|
qCDebug(chatterinoImage) << "freed frame data for" << numExpired << "/"
|
||||||
<< eligible << "eligible images";
|
<< eligible << "eligible images";
|
||||||
# endif
|
# endif
|
||||||
|
DebugCount::set("last image gc: expired", numExpired);
|
||||||
|
DebugCount::set("last image gc: eligible", eligible);
|
||||||
|
DebugCount::set("last image gc: left after gc", this->allImages_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -41,6 +41,7 @@ namespace detail {
|
||||||
boost::optional<QPixmap> first() const;
|
boost::optional<QPixmap> first() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int64_t memoryUsage() const;
|
||||||
void processOffset();
|
void processOffset();
|
||||||
QVector<Frame<QPixmap>> items_;
|
QVector<Frame<QPixmap>> items_;
|
||||||
int index_{0};
|
int index_{0};
|
||||||
|
@ -111,6 +112,7 @@ class ImageExpirationPool
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
friend class Image;
|
friend class Image;
|
||||||
|
friend class CommandController;
|
||||||
|
|
||||||
ImageExpirationPool();
|
ImageExpirationPool();
|
||||||
static ImageExpirationPool &instance();
|
static ImageExpirationPool &instance();
|
||||||
|
@ -126,6 +128,12 @@ private:
|
||||||
*/
|
*/
|
||||||
void freeOld();
|
void freeOld();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Debug function that unloads all images in the pool. This is intended to
|
||||||
|
* test for possible memory leaks from tracked images.
|
||||||
|
*/
|
||||||
|
void freeAll();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Timer to periodically run freeOld()
|
// Timer to periodically run freeOld()
|
||||||
QTimer *freeTimer_;
|
QTimer *freeTimer_;
|
||||||
|
|
|
@ -46,7 +46,7 @@ enum class MessageFlag : int64_t {
|
||||||
FirstMessage = (1LL << 23),
|
FirstMessage = (1LL << 23),
|
||||||
ReplyMessage = (1LL << 24),
|
ReplyMessage = (1LL << 24),
|
||||||
ElevatedMessage = (1LL << 25),
|
ElevatedMessage = (1LL << 25),
|
||||||
ParticipatedThread = (1LL << 26),
|
SubscribedThread = (1LL << 26),
|
||||||
CheerMessage = (1LL << 27),
|
CheerMessage = (1LL << 27),
|
||||||
LiveUpdatesAdd = (1LL << 28),
|
LiveUpdatesAdd = (1LL << 28),
|
||||||
LiveUpdatesRemove = (1LL << 29),
|
LiveUpdatesRemove = (1LL << 29),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "MessageThread.hpp"
|
#include "messages/MessageThread.hpp"
|
||||||
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
|
@ -58,14 +58,26 @@ size_t MessageThread::liveCount(
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageThread::participated() const
|
void MessageThread::markSubscribed()
|
||||||
{
|
{
|
||||||
return this->participated_;
|
if (this->subscription_ == Subscription::Subscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->subscription_ = Subscription::Subscribed;
|
||||||
|
this->subscriptionUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageThread::markParticipated()
|
void MessageThread::markUnsubscribed()
|
||||||
{
|
{
|
||||||
this->participated_ = true;
|
if (this->subscription_ == Subscription::Unsubscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->subscription_ = Subscription::Unsubscribed;
|
||||||
|
this->subscriptionUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/signals2.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -11,6 +12,12 @@ struct Message;
|
||||||
class MessageThread
|
class MessageThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
enum class Subscription : uint8_t {
|
||||||
|
None,
|
||||||
|
Subscribed,
|
||||||
|
Unsubscribed,
|
||||||
|
};
|
||||||
|
|
||||||
MessageThread(std::shared_ptr<const Message> rootMessage);
|
MessageThread(std::shared_ptr<const Message> rootMessage);
|
||||||
~MessageThread();
|
~MessageThread();
|
||||||
|
|
||||||
|
@ -23,9 +30,22 @@ public:
|
||||||
/// Returns the number of live reply references
|
/// Returns the number of live reply references
|
||||||
size_t liveCount(const std::shared_ptr<const Message> &exclude) const;
|
size_t liveCount(const std::shared_ptr<const Message> &exclude) const;
|
||||||
|
|
||||||
bool participated() const;
|
bool subscribed() const
|
||||||
|
{
|
||||||
|
return this->subscription_ == Subscription::Subscribed;
|
||||||
|
}
|
||||||
|
|
||||||
void markParticipated();
|
/// Returns true if and only if the user manually unsubscribed from the thread
|
||||||
|
/// @see #markUnsubscribed()
|
||||||
|
bool unsubscribed() const
|
||||||
|
{
|
||||||
|
return this->subscription_ == Subscription::Unsubscribed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subscribe to this thread.
|
||||||
|
void markSubscribed();
|
||||||
|
/// Unsubscribe from this thread.
|
||||||
|
void markUnsubscribed();
|
||||||
|
|
||||||
const QString &rootId() const
|
const QString &rootId() const
|
||||||
{
|
{
|
||||||
|
@ -42,11 +62,14 @@ public:
|
||||||
return replies_;
|
return replies_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::signals2::signal<void()> subscriptionUpdated;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QString rootMessageId_;
|
const QString rootMessageId_;
|
||||||
const std::shared_ptr<const Message> rootMessage_;
|
const std::shared_ptr<const Message> rootMessage_;
|
||||||
std::vector<std::weak_ptr<const Message>> replies_;
|
std::vector<std::weak_ptr<const Message>> replies_;
|
||||||
bool participated_ = false;
|
|
||||||
|
Subscription subscription_ = Subscription::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -78,6 +78,7 @@ struct Selection {
|
||||||
if (offset > this->selectionMin.messageIndex)
|
if (offset > this->selectionMin.messageIndex)
|
||||||
{
|
{
|
||||||
this->selectionMin.messageIndex = 0;
|
this->selectionMin.messageIndex = 0;
|
||||||
|
this->selectionMin.charIndex = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -87,6 +88,7 @@ struct Selection {
|
||||||
if (offset > this->selectionMax.messageIndex)
|
if (offset > this->selectionMax.messageIndex)
|
||||||
{
|
{
|
||||||
this->selectionMax.messageIndex = 0;
|
this->selectionMax.messageIndex = 0;
|
||||||
|
this->selectionMax.charIndex = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -96,6 +98,7 @@ struct Selection {
|
||||||
if (offset > this->start.messageIndex)
|
if (offset > this->start.messageIndex)
|
||||||
{
|
{
|
||||||
this->start.messageIndex = 0;
|
this->start.messageIndex = 0;
|
||||||
|
this->start.charIndex = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -105,6 +108,7 @@ struct Selection {
|
||||||
if (offset > this->end.messageIndex)
|
if (offset > this->end.messageIndex)
|
||||||
{
|
{
|
||||||
this->end.messageIndex = 0;
|
this->end.messageIndex = 0;
|
||||||
|
this->end.charIndex = 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -270,14 +270,9 @@ QString SharedMessageBuilder::stylizeUsername(const QString &username,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto nicknames = getCSettings().nicknames.readOnly();
|
if (auto nicknameText = getCSettings().matchNickname(usernameText))
|
||||||
|
|
||||||
for (const auto &nickname : *nicknames)
|
|
||||||
{
|
{
|
||||||
if (nickname.match(usernameText))
|
usernameText = *nicknameText;
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return usernameText;
|
return usernameText;
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
#include "messages/layouts/MessageLayout.hpp"
|
#include "messages/layouts/MessageLayout.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
|
||||||
#include "messages/layouts/MessageLayoutContainer.hpp"
|
#include "messages/layouts/MessageLayoutContainer.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "messages/Selection.hpp"
|
#include "messages/Selection.hpp"
|
||||||
#include "providers/colors/ColorProvider.hpp"
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/StreamerMode.hpp"
|
#include "util/StreamerMode.hpp"
|
||||||
|
@ -198,84 +196,77 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
void MessageLayout::paint(const MessagePaintContext &ctx)
|
||||||
Selection &selection, bool isLastReadMessage,
|
|
||||||
bool isWindowFocused, bool isMentions)
|
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
QPixmap *pixmap = this->ensureBuffer(ctx.painter, ctx.canvasWidth);
|
||||||
QPixmap *pixmap = this->ensureBuffer(painter, width);
|
|
||||||
|
|
||||||
if (!this->bufferValid_ || !selection.isEmpty())
|
if (!this->bufferValid_ || !ctx.selection.isEmpty())
|
||||||
{
|
{
|
||||||
this->updateBuffer(pixmap, messageIndex, selection);
|
this->updateBuffer(pixmap, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw on buffer
|
// draw on buffer
|
||||||
painter.drawPixmap(0, y, *pixmap);
|
ctx.painter.drawPixmap(0, ctx.y, *pixmap);
|
||||||
// painter.drawPixmap(0, y, this->container.width,
|
|
||||||
// this->container.getHeight(), *pixmap);
|
|
||||||
|
|
||||||
// draw gif emotes
|
// draw gif emotes
|
||||||
this->container_.paintAnimatedElements(painter, y);
|
this->container_.paintAnimatedElements(ctx.painter, ctx.y);
|
||||||
|
|
||||||
// draw disabled
|
// draw disabled
|
||||||
if (this->message_->flags.has(MessageFlag::Disabled))
|
if (this->message_->flags.has(MessageFlag::Disabled))
|
||||||
{
|
{
|
||||||
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
ctx.painter.fillRect(0, ctx.y, pixmap->width(), pixmap->height(),
|
||||||
app->themes->messages.disabled);
|
ctx.messageColors.disabled);
|
||||||
// painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
|
||||||
// QBrush(QColor(64, 64, 64, 64)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->message_->flags.has(MessageFlag::RecentMessage))
|
if (this->message_->flags.has(MessageFlag::RecentMessage))
|
||||||
{
|
{
|
||||||
painter.fillRect(0, y, pixmap->width(), pixmap->height(),
|
ctx.painter.fillRect(0, ctx.y, pixmap->width(), pixmap->height(),
|
||||||
app->themes->messages.disabled);
|
ctx.messageColors.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isMentions &&
|
if (!ctx.isMentions &&
|
||||||
(this->message_->flags.has(MessageFlag::RedeemedChannelPointReward) ||
|
(this->message_->flags.has(MessageFlag::RedeemedChannelPointReward) ||
|
||||||
this->message_->flags.has(MessageFlag::RedeemedHighlight)) &&
|
this->message_->flags.has(MessageFlag::RedeemedHighlight)) &&
|
||||||
getSettings()->enableRedeemedHighlight.getValue())
|
ctx.preferences.enableRedeemedHighlight)
|
||||||
{
|
{
|
||||||
painter.fillRect(
|
ctx.painter.fillRect(
|
||||||
0, y, this->scale_ * 4, pixmap->height(),
|
0, ctx.y, int(this->scale_ * 4), pixmap->height(),
|
||||||
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
|
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw selection
|
// draw selection
|
||||||
if (!selection.isEmpty())
|
if (!ctx.selection.isEmpty())
|
||||||
{
|
{
|
||||||
this->container_.paintSelection(painter, messageIndex, selection, y);
|
this->container_.paintSelection(ctx.painter, ctx.messageIndex,
|
||||||
|
ctx.selection, ctx.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw message seperation line
|
// draw message seperation line
|
||||||
if (getSettings()->separateMessages.getValue())
|
if (ctx.preferences.separateMessages)
|
||||||
{
|
{
|
||||||
painter.fillRect(0, y, this->container_.getWidth() + 64, 1,
|
ctx.painter.fillRect(0, ctx.y, this->container_.getWidth() + 64, 1,
|
||||||
app->themes->splits.messageSeperator);
|
ctx.messageColors.messageSeperator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw last read message line
|
// draw last read message line
|
||||||
if (isLastReadMessage)
|
if (ctx.isLastReadMessage)
|
||||||
{
|
{
|
||||||
QColor color;
|
QColor color;
|
||||||
if (getSettings()->lastMessageColor != QStringLiteral(""))
|
if (ctx.preferences.lastMessageColor.isValid())
|
||||||
{
|
{
|
||||||
color = QColor(getSettings()->lastMessageColor.getValue());
|
color = ctx.preferences.lastMessageColor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
color = isWindowFocused
|
color = ctx.isWindowFocused
|
||||||
? app->themes->tabs.selected.backgrounds.regular
|
? ctx.messageColors.focusedLastMessageLine
|
||||||
: app->themes->tabs.selected.backgrounds.unfocused;
|
: ctx.messageColors.unfocusedLastMessageLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
QBrush brush(color, static_cast<Qt::BrushStyle>(
|
QBrush brush(color, ctx.preferences.lastMessagePattern);
|
||||||
getSettings()->lastMessagePattern.getValue()));
|
|
||||||
|
|
||||||
painter.fillRect(0, y + this->container_.getHeight() - 1,
|
ctx.painter.fillRect(0, ctx.y + this->container_.getHeight() - 1,
|
||||||
pixmap->width(), 1, brush);
|
pixmap->width(), 1, brush);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->bufferValid_ = true;
|
this->bufferValid_ = true;
|
||||||
|
@ -305,45 +296,42 @@ QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width)
|
||||||
return this->buffer_.get();
|
return this->buffer_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
void MessageLayout::updateBuffer(QPixmap *buffer,
|
||||||
Selection & /*selection*/)
|
const MessagePaintContext &ctx)
|
||||||
{
|
{
|
||||||
if (buffer->isNull())
|
if (buffer->isNull())
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
auto app = getApp();
|
|
||||||
auto settings = getSettings();
|
|
||||||
|
|
||||||
QPainter painter(buffer);
|
QPainter painter(buffer);
|
||||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
|
||||||
// draw background
|
// draw background
|
||||||
QColor backgroundColor = [this, &app] {
|
QColor backgroundColor = [&] {
|
||||||
if (getSettings()->alternateMessages.getValue() &&
|
if (ctx.preferences.alternateMessages &&
|
||||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
||||||
{
|
{
|
||||||
return app->themes->messages.backgrounds.alternate;
|
return ctx.messageColors.alternate;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return app->themes->messages.backgrounds.regular;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ctx.messageColors.regular;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (this->message_->flags.has(MessageFlag::ElevatedMessage) &&
|
if (this->message_->flags.has(MessageFlag::ElevatedMessage) &&
|
||||||
getSettings()->enableElevatedMessageHighlight.getValue())
|
ctx.preferences.enableElevatedMessageHighlight)
|
||||||
{
|
|
||||||
backgroundColor = blendColors(backgroundColor,
|
|
||||||
*ColorProvider::instance().color(
|
|
||||||
ColorType::ElevatedMessageHighlight));
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (this->message_->flags.has(MessageFlag::FirstMessage) &&
|
|
||||||
getSettings()->enableFirstMessageHighlight.getValue())
|
|
||||||
{
|
{
|
||||||
backgroundColor = blendColors(
|
backgroundColor = blendColors(
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
*ColorProvider::instance().color(ColorType::FirstMessageHighlight));
|
*ctx.colorProvider.color(ColorType::ElevatedMessageHighlight));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (this->message_->flags.has(MessageFlag::FirstMessage) &&
|
||||||
|
ctx.preferences.enableFirstMessageHighlight)
|
||||||
|
{
|
||||||
|
backgroundColor = blendColors(
|
||||||
|
backgroundColor,
|
||||||
|
*ctx.colorProvider.color(ColorType::FirstMessageHighlight));
|
||||||
}
|
}
|
||||||
else if ((this->message_->flags.has(MessageFlag::Highlighted) ||
|
else if ((this->message_->flags.has(MessageFlag::Highlighted) ||
|
||||||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
||||||
|
@ -354,22 +342,21 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
||||||
blendColors(backgroundColor, *this->message_->highlightColor);
|
blendColors(backgroundColor, *this->message_->highlightColor);
|
||||||
}
|
}
|
||||||
else if (this->message_->flags.has(MessageFlag::Subscription) &&
|
else if (this->message_->flags.has(MessageFlag::Subscription) &&
|
||||||
getSettings()->enableSubHighlight)
|
ctx.preferences.enableSubHighlight)
|
||||||
{
|
{
|
||||||
// Blend highlight color with usual background color
|
// Blend highlight color with usual background color
|
||||||
backgroundColor = blendColors(
|
backgroundColor = blendColors(
|
||||||
backgroundColor,
|
backgroundColor, *ctx.colorProvider.color(ColorType::Subscription));
|
||||||
*ColorProvider::instance().color(ColorType::Subscription));
|
|
||||||
}
|
}
|
||||||
else if ((this->message_->flags.has(MessageFlag::RedeemedHighlight) ||
|
else if ((this->message_->flags.has(MessageFlag::RedeemedHighlight) ||
|
||||||
this->message_->flags.has(
|
this->message_->flags.has(
|
||||||
MessageFlag::RedeemedChannelPointReward)) &&
|
MessageFlag::RedeemedChannelPointReward)) &&
|
||||||
settings->enableRedeemedHighlight.getValue())
|
ctx.preferences.enableRedeemedHighlight)
|
||||||
{
|
{
|
||||||
// Blend highlight color with usual background color
|
// Blend highlight color with usual background color
|
||||||
backgroundColor = blendColors(
|
backgroundColor =
|
||||||
backgroundColor,
|
blendColors(backgroundColor,
|
||||||
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
|
*ctx.colorProvider.color(ColorType::RedeemedHighlight));
|
||||||
}
|
}
|
||||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||||
{
|
{
|
||||||
|
@ -383,7 +370,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
||||||
painter.fillRect(buffer->rect(), backgroundColor);
|
painter.fillRect(buffer->rect(), backgroundColor);
|
||||||
|
|
||||||
// draw message
|
// draw message
|
||||||
this->container_.paintElements(painter);
|
this->container_.paintElements(painter, ctx);
|
||||||
|
|
||||||
#ifdef FOURTF
|
#ifdef FOURTF
|
||||||
// debug
|
// debug
|
||||||
|
|
|
@ -18,6 +18,7 @@ using MessagePtr = std::shared_ptr<const Message>;
|
||||||
struct Selection;
|
struct Selection;
|
||||||
struct MessageLayoutContainer;
|
struct MessageLayoutContainer;
|
||||||
class MessageLayoutElement;
|
class MessageLayoutElement;
|
||||||
|
struct MessagePaintContext;
|
||||||
|
|
||||||
enum class MessageElementFlag : int64_t;
|
enum class MessageElementFlag : int64_t;
|
||||||
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
||||||
|
@ -49,9 +50,7 @@ public:
|
||||||
bool layout(int width, float scale_, MessageElementFlags flags);
|
bool layout(int width, float scale_, MessageElementFlags flags);
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
void paint(QPainter &painter, int width, int y, int messageIndex,
|
void paint(const MessagePaintContext &ctx);
|
||||||
Selection &selection, bool isLastReadMessage,
|
|
||||||
bool isWindowFocused, bool isMentions);
|
|
||||||
void invalidateBuffer();
|
void invalidateBuffer();
|
||||||
void deleteBuffer();
|
void deleteBuffer();
|
||||||
void deleteCache();
|
void deleteCache();
|
||||||
|
@ -72,7 +71,7 @@ public:
|
||||||
private:
|
private:
|
||||||
// methods
|
// methods
|
||||||
void actuallyLayout(int width, MessageElementFlags flags);
|
void actuallyLayout(int width, MessageElementFlags flags);
|
||||||
void updateBuffer(QPixmap *pixmap, int messageIndex, Selection &selection);
|
void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx);
|
||||||
|
|
||||||
// Create new buffer if required, returning the buffer
|
// Create new buffer if required, returning the buffer
|
||||||
QPixmap *ensureBuffer(QPainter &painter, int width);
|
QPixmap *ensureBuffer(QPainter &painter, int width);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "MessageLayoutContainer.hpp"
|
#include "MessageLayoutContainer.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -151,7 +152,7 @@ void MessageLayoutContainer::_addElement(MessageLayoutElement *element,
|
||||||
}
|
}
|
||||||
|
|
||||||
// top margin
|
// top margin
|
||||||
if (this->elements_.size() == 0)
|
if (this->elements_.empty())
|
||||||
{
|
{
|
||||||
this->currentY_ = int(this->margin.top * this->scale_);
|
this->currentY_ = int(this->margin.top * this->scale_);
|
||||||
}
|
}
|
||||||
|
@ -276,17 +277,24 @@ void MessageLayoutContainer::reorderRTL(int firstTextIndex)
|
||||||
// 2 - in LTR mode, the previous word should be RTL (i.e. reversed)
|
// 2 - in LTR mode, the previous word should be RTL (i.e. reversed)
|
||||||
for (int i = startIndex; i <= endIndex; i++)
|
for (int i = startIndex; i <= endIndex; i++)
|
||||||
{
|
{
|
||||||
if (isNeutral(this->elements_[i]->getText()) &&
|
auto &element = this->elements_[i];
|
||||||
|
|
||||||
|
const auto neutral = isNeutral(element->getText());
|
||||||
|
const auto neutralOrUsername =
|
||||||
|
neutral ||
|
||||||
|
element->getFlags().hasAny({MessageElementFlag::BoldUsername,
|
||||||
|
MessageElementFlag::NonBoldUsername});
|
||||||
|
|
||||||
|
if (neutral &&
|
||||||
((this->first == FirstWord::RTL && !this->wasPrevReversed_) ||
|
((this->first == FirstWord::RTL && !this->wasPrevReversed_) ||
|
||||||
(this->first == FirstWord::LTR && this->wasPrevReversed_)))
|
(this->first == FirstWord::LTR && this->wasPrevReversed_)))
|
||||||
{
|
{
|
||||||
this->elements_[i]->reversedNeutral = true;
|
element->reversedNeutral = true;
|
||||||
}
|
}
|
||||||
if (((this->elements_[i]->getText().isRightToLeft() !=
|
if (((element->getText().isRightToLeft() !=
|
||||||
(this->first == FirstWord::RTL)) &&
|
(this->first == FirstWord::RTL)) &&
|
||||||
!isNeutral(this->elements_[i]->getText())) ||
|
!neutralOrUsername) ||
|
||||||
(isNeutral(this->elements_[i]->getText()) &&
|
(neutralOrUsername && this->wasPrevReversed_))
|
||||||
this->wasPrevReversed_))
|
|
||||||
{
|
{
|
||||||
swappedSequence.push(i);
|
swappedSequence.push(i);
|
||||||
this->wasPrevReversed_ = true;
|
this->wasPrevReversed_ = true;
|
||||||
|
@ -379,7 +387,7 @@ void MessageLayoutContainer::breakLine()
|
||||||
element->getRect().y() + this->lineHeight_ + yExtra));
|
element->getRect().y() + this->lineHeight_ + yExtra));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->lines_.size() != 0)
|
if (!this->lines_.empty())
|
||||||
{
|
{
|
||||||
this->lines_.back().endIndex = this->lineStart_;
|
this->lines_.back().endIndex = this->lineStart_;
|
||||||
this->lines_.back().endCharIndex = this->charIndex_;
|
this->lines_.back().endCharIndex = this->charIndex_;
|
||||||
|
@ -388,7 +396,7 @@ void MessageLayoutContainer::breakLine()
|
||||||
{(int)lineStart_, 0, this->charIndex_, 0,
|
{(int)lineStart_, 0, this->charIndex_, 0,
|
||||||
QRect(-100000, this->currentY_, 200000, lineHeight_)});
|
QRect(-100000, this->currentY_, 200000, lineHeight_)});
|
||||||
|
|
||||||
for (int i = this->lineStart_; i < this->elements_.size(); i++)
|
for (auto i = this->lineStart_; i < this->elements_.size(); i++)
|
||||||
{
|
{
|
||||||
this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
|
this->charIndex_ += this->elements_[i]->getSelectionIndexCount();
|
||||||
}
|
}
|
||||||
|
@ -458,7 +466,7 @@ void MessageLayoutContainer::end()
|
||||||
|
|
||||||
this->height_ += this->lineHeight_;
|
this->height_ += this->lineHeight_;
|
||||||
|
|
||||||
if (this->lines_.size() != 0)
|
if (!this->lines_.empty())
|
||||||
{
|
{
|
||||||
this->lines_[0].rect.setTop(-100000);
|
this->lines_[0].rect.setTop(-100000);
|
||||||
this->lines_.back().rect.setBottom(100000);
|
this->lines_.back().rect.setBottom(100000);
|
||||||
|
@ -473,7 +481,7 @@ bool MessageLayoutContainer::canCollapse()
|
||||||
this->flags_.has(MessageFlag::Collapsed);
|
this->flags_.has(MessageFlag::Collapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MessageLayoutContainer::isCollapsed()
|
bool MessageLayoutContainer::isCollapsed() const
|
||||||
{
|
{
|
||||||
return this->isCollapsed_;
|
return this->isCollapsed_;
|
||||||
}
|
}
|
||||||
|
@ -492,7 +500,8 @@ MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
|
||||||
}
|
}
|
||||||
|
|
||||||
// painting
|
// painting
|
||||||
void MessageLayoutContainer::paintElements(QPainter &painter)
|
void MessageLayoutContainer::paintElements(QPainter &painter,
|
||||||
|
const MessagePaintContext &ctx)
|
||||||
{
|
{
|
||||||
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_)
|
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements_)
|
||||||
{
|
{
|
||||||
|
@ -501,7 +510,7 @@ void MessageLayoutContainer::paintElements(QPainter &painter)
|
||||||
painter.drawRect(element->getRect());
|
painter.drawRect(element->getRect());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
element->paint(painter);
|
element->paint(painter, ctx.messageColors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,10 +523,12 @@ void MessageLayoutContainer::paintAnimatedElements(QPainter &painter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
void MessageLayoutContainer::paintSelection(QPainter &painter,
|
||||||
Selection &selection, int yOffset)
|
size_t messageIndex,
|
||||||
|
const Selection &selection,
|
||||||
|
int yOffset)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto *app = getApp();
|
||||||
QColor selectionColor = app->themes->messages.selection;
|
QColor selectionColor = app->themes->messages.selection;
|
||||||
|
|
||||||
// don't draw anything
|
// don't draw anything
|
||||||
|
@ -706,7 +717,7 @@ void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||||
// selection
|
// selection
|
||||||
int MessageLayoutContainer::getSelectionIndex(QPoint point)
|
int MessageLayoutContainer::getSelectionIndex(QPoint point)
|
||||||
{
|
{
|
||||||
if (this->elements_.size() == 0)
|
if (this->elements_.empty())
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -767,7 +778,7 @@ int MessageLayoutContainer::getSelectionIndex(QPoint point)
|
||||||
// fourtf: no idea if this is acurate LOL
|
// fourtf: no idea if this is acurate LOL
|
||||||
int MessageLayoutContainer::getLastCharacterIndex() const
|
int MessageLayoutContainer::getLastCharacterIndex() const
|
||||||
{
|
{
|
||||||
if (this->lines_.size() == 0)
|
if (this->lines_.empty())
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -784,7 +795,7 @@ int MessageLayoutContainer::getFirstMessageCharacterIndex() const
|
||||||
|
|
||||||
// Get the index of the first character of the real message
|
// Get the index of the first character of the real message
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (auto &element : this->elements_)
|
for (const auto &element : this->elements_)
|
||||||
{
|
{
|
||||||
if (element->getFlags().hasAny(skippedFlags))
|
if (element->getFlags().hasAny(skippedFlags))
|
||||||
{
|
{
|
||||||
|
@ -846,10 +857,8 @@ void MessageLayoutContainer::addSelectionText(QString &str, uint32_t from,
|
||||||
element->addCopyTextToString(str, 0, to - index);
|
element->addCopyTextToString(str, 0, to - index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
element->addCopyTextToString(str);
|
||||||
element->addCopyTextToString(str);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
index += indexCount;
|
index += indexCount;
|
||||||
|
|
|
@ -18,6 +18,7 @@ enum class FirstWord { Neutral, RTL, LTR };
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
class MessageLayoutElement;
|
class MessageLayoutElement;
|
||||||
struct Selection;
|
struct Selection;
|
||||||
|
struct MessagePaintContext;
|
||||||
|
|
||||||
struct Margin {
|
struct Margin {
|
||||||
int top;
|
int top;
|
||||||
|
@ -73,10 +74,10 @@ struct MessageLayoutContainer {
|
||||||
MessageLayoutElement *getElementAt(QPoint point);
|
MessageLayoutElement *getElementAt(QPoint point);
|
||||||
|
|
||||||
// painting
|
// painting
|
||||||
void paintElements(QPainter &painter);
|
void paintElements(QPainter &painter, const MessagePaintContext &ctx);
|
||||||
void paintAnimatedElements(QPainter &painter, int yOffset);
|
void paintAnimatedElements(QPainter &painter, int yOffset);
|
||||||
void paintSelection(QPainter &painter, int messageIndex,
|
void paintSelection(QPainter &painter, size_t messageIndex,
|
||||||
Selection &selection, int yOffset);
|
const Selection &selection, int yOffset);
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
int getSelectionIndex(QPoint point);
|
int getSelectionIndex(QPoint point);
|
||||||
|
@ -85,7 +86,7 @@ struct MessageLayoutContainer {
|
||||||
void addSelectionText(QString &str, uint32_t from, uint32_t to,
|
void addSelectionText(QString &str, uint32_t from, uint32_t to,
|
||||||
CopyMode copymode);
|
CopyMode copymode);
|
||||||
|
|
||||||
bool isCollapsed();
|
bool isCollapsed() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Line {
|
struct Line {
|
||||||
|
|
82
src/messages/layouts/MessageLayoutContext.cpp
Normal file
82
src/messages/layouts/MessageLayoutContext.cpp
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
|
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void MessageColors::applyTheme(Theme *theme)
|
||||||
|
{
|
||||||
|
this->regular = theme->messages.backgrounds.regular;
|
||||||
|
this->alternate = theme->messages.backgrounds.alternate;
|
||||||
|
|
||||||
|
this->disabled = theme->messages.disabled;
|
||||||
|
this->selection = theme->messages.selection;
|
||||||
|
this->system = theme->messages.textColors.system;
|
||||||
|
|
||||||
|
this->messageSeperator = theme->splits.messageSeperator;
|
||||||
|
|
||||||
|
this->focusedLastMessageLine = theme->tabs.selected.backgrounds.regular;
|
||||||
|
this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagePreferences::connectSettings(Settings *settings,
|
||||||
|
pajlada::Signals::SignalHolder &holder)
|
||||||
|
{
|
||||||
|
settings->enableRedeemedHighlight.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->enableRedeemedHighlight = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->enableElevatedMessageHighlight.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->enableElevatedMessageHighlight = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->enableFirstMessageHighlight.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->enableFirstMessageHighlight = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->enableSubHighlight.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->enableSubHighlight = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->alternateMessages.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->alternateMessages = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->separateMessages.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->separateMessages = newValue;
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->lastMessageColor.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
if (newValue.isEmpty())
|
||||||
|
{
|
||||||
|
this->lastMessageColor = QColor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->lastMessageColor = QColor(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
|
||||||
|
settings->lastMessagePattern.connect(
|
||||||
|
[this](const auto &newValue) {
|
||||||
|
this->lastMessagePattern = static_cast<Qt::BrushStyle>(newValue);
|
||||||
|
},
|
||||||
|
holder);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
74
src/messages/layouts/MessageLayoutContext.hpp
Normal file
74
src/messages/layouts/MessageLayoutContext.hpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
namespace pajlada::Signals {
|
||||||
|
class SignalHolder;
|
||||||
|
} // namespace pajlada::Signals
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class ColorProvider;
|
||||||
|
class Theme;
|
||||||
|
class Settings;
|
||||||
|
struct Selection;
|
||||||
|
|
||||||
|
// TODO: Figure out if this could be a subset of Theme instead (e.g. Theme::MessageColors)
|
||||||
|
struct MessageColors {
|
||||||
|
QColor regular;
|
||||||
|
QColor alternate;
|
||||||
|
QColor disabled;
|
||||||
|
QColor selection;
|
||||||
|
QColor system;
|
||||||
|
|
||||||
|
QColor messageSeperator;
|
||||||
|
|
||||||
|
QColor focusedLastMessageLine;
|
||||||
|
QColor unfocusedLastMessageLine;
|
||||||
|
|
||||||
|
void applyTheme(Theme *theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Explore if we can let settings own this
|
||||||
|
struct MessagePreferences {
|
||||||
|
QColor lastMessageColor;
|
||||||
|
Qt::BrushStyle lastMessagePattern{};
|
||||||
|
|
||||||
|
bool enableRedeemedHighlight{};
|
||||||
|
bool enableElevatedMessageHighlight{};
|
||||||
|
bool enableFirstMessageHighlight{};
|
||||||
|
bool enableSubHighlight{};
|
||||||
|
|
||||||
|
bool alternateMessages{};
|
||||||
|
bool separateMessages{};
|
||||||
|
|
||||||
|
void connectSettings(Settings *settings,
|
||||||
|
pajlada::Signals::SignalHolder &holder);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessagePaintContext {
|
||||||
|
QPainter &painter;
|
||||||
|
const Selection &selection;
|
||||||
|
const ColorProvider &colorProvider;
|
||||||
|
const MessageColors &messageColors;
|
||||||
|
const MessagePreferences &preferences;
|
||||||
|
|
||||||
|
// width of the area we have to draw on
|
||||||
|
const int canvasWidth{};
|
||||||
|
// whether the painting should be treated as if this view's window is focused
|
||||||
|
const bool isWindowFocused{};
|
||||||
|
// whether the painting should be treated as if this view is the special mentions view
|
||||||
|
const bool isMentions{};
|
||||||
|
|
||||||
|
// y coordinate we're currently painting at
|
||||||
|
int y{};
|
||||||
|
|
||||||
|
// Index of the message that is currently being painted
|
||||||
|
// This index refers to the snapshot being used in the painting
|
||||||
|
size_t messageIndex{};
|
||||||
|
|
||||||
|
bool isLastReadMessage{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -3,9 +3,9 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
@ -137,7 +137,8 @@ int ImageLayoutElement::getSelectionIndexCount() const
|
||||||
return this->trailingSpace ? 2 : 1;
|
return this->trailingSpace ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageLayoutElement::paint(QPainter &painter)
|
void ImageLayoutElement::paint(QPainter &painter,
|
||||||
|
const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
if (this->image_ == nullptr)
|
if (this->image_ == nullptr)
|
||||||
{
|
{
|
||||||
|
@ -228,7 +229,8 @@ int LayeredImageLayoutElement::getSelectionIndexCount() const
|
||||||
return this->trailingSpace ? 2 : 1;
|
return this->trailingSpace ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayeredImageLayoutElement::paint(QPainter &painter)
|
void LayeredImageLayoutElement::paint(QPainter &painter,
|
||||||
|
const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
auto fullRect = QRectF(this->getRect());
|
auto fullRect = QRectF(this->getRect());
|
||||||
|
|
||||||
|
@ -329,7 +331,8 @@ ImageWithBackgroundLayoutElement::ImageWithBackgroundLayoutElement(
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageWithBackgroundLayoutElement::paint(QPainter &painter)
|
void ImageWithBackgroundLayoutElement::paint(
|
||||||
|
QPainter &painter, const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
if (this->image_ == nullptr)
|
if (this->image_ == nullptr)
|
||||||
{
|
{
|
||||||
|
@ -360,7 +363,8 @@ ImageWithCircleBackgroundLayoutElement::ImageWithCircleBackgroundLayoutElement(
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageWithCircleBackgroundLayoutElement::paint(QPainter &painter)
|
void ImageWithCircleBackgroundLayoutElement::paint(
|
||||||
|
QPainter &painter, const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
if (this->image_ == nullptr)
|
if (this->image_ == nullptr)
|
||||||
{
|
{
|
||||||
|
@ -423,7 +427,8 @@ int TextLayoutElement::getSelectionIndexCount() const
|
||||||
return this->getText().length() + (this->trailingSpace ? 1 : 0);
|
return this->getText().length() + (this->trailingSpace ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextLayoutElement::paint(QPainter &painter)
|
void TextLayoutElement::paint(QPainter &painter,
|
||||||
|
const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
QString text = this->getText();
|
QString text = this->getText();
|
||||||
|
@ -532,13 +537,14 @@ int TextIconLayoutElement::getSelectionIndexCount() const
|
||||||
return this->trailingSpace ? 2 : 1;
|
return this->trailingSpace ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextIconLayoutElement::paint(QPainter &painter)
|
void TextIconLayoutElement::paint(QPainter &painter,
|
||||||
|
const MessageColors &messageColors)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto *app = getApp();
|
||||||
|
|
||||||
QFont font = app->fonts->getFont(FontStyle::Tiny, this->scale);
|
QFont font = app->fonts->getFont(FontStyle::Tiny, this->scale);
|
||||||
|
|
||||||
painter.setPen(app->themes->messages.textColors.system);
|
painter.setPen(messageColors.system);
|
||||||
painter.setFont(font);
|
painter.setFont(font);
|
||||||
|
|
||||||
QTextOption option;
|
QTextOption option;
|
||||||
|
@ -598,7 +604,8 @@ ReplyCurveLayoutElement::ReplyCurveLayoutElement(MessageElement &creator,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplyCurveLayoutElement::paint(QPainter &painter)
|
void ReplyCurveLayoutElement::paint(QPainter &painter,
|
||||||
|
const MessageColors & /*messageColors*/)
|
||||||
{
|
{
|
||||||
QRectF paintRect(this->getRect());
|
QRectF paintRect(this->getRect());
|
||||||
QPainterPath path;
|
QPainterPath path;
|
||||||
|
|
|
@ -21,6 +21,7 @@ class Image;
|
||||||
using ImagePtr = std::shared_ptr<Image>;
|
using ImagePtr = std::shared_ptr<Image>;
|
||||||
enum class FontStyle : uint8_t;
|
enum class FontStyle : uint8_t;
|
||||||
enum class MessageElementFlag : int64_t;
|
enum class MessageElementFlag : int64_t;
|
||||||
|
struct MessageColors;
|
||||||
|
|
||||||
class MessageLayoutElement : boost::noncopyable
|
class MessageLayoutElement : boost::noncopyable
|
||||||
{
|
{
|
||||||
|
@ -44,7 +45,8 @@ public:
|
||||||
virtual void addCopyTextToString(QString &str, uint32_t from = 0,
|
virtual void addCopyTextToString(QString &str, uint32_t from = 0,
|
||||||
uint32_t to = UINT32_MAX) const = 0;
|
uint32_t to = UINT32_MAX) const = 0;
|
||||||
virtual int getSelectionIndexCount() const = 0;
|
virtual int getSelectionIndexCount() const = 0;
|
||||||
virtual void paint(QPainter &painter) = 0;
|
virtual void paint(QPainter &painter,
|
||||||
|
const MessageColors &messageColors) = 0;
|
||||||
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
virtual void paintAnimated(QPainter &painter, int yOffset) = 0;
|
||||||
virtual int getMouseOverIndex(const QPoint &abs) const = 0;
|
virtual int getMouseOverIndex(const QPoint &abs) const = 0;
|
||||||
virtual int getXFromIndex(int index) = 0;
|
virtual int getXFromIndex(int index) = 0;
|
||||||
|
@ -75,7 +77,7 @@ protected:
|
||||||
void addCopyTextToString(QString &str, uint32_t from = 0,
|
void addCopyTextToString(QString &str, uint32_t from = 0,
|
||||||
uint32_t to = UINT32_MAX) const override;
|
uint32_t to = UINT32_MAX) const override;
|
||||||
int getSelectionIndexCount() const override;
|
int getSelectionIndexCount() const override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
int getMouseOverIndex(const QPoint &abs) const override;
|
int getMouseOverIndex(const QPoint &abs) const override;
|
||||||
int getXFromIndex(int index) override;
|
int getXFromIndex(int index) override;
|
||||||
|
@ -94,7 +96,7 @@ protected:
|
||||||
void addCopyTextToString(QString &str, uint32_t from = 0,
|
void addCopyTextToString(QString &str, uint32_t from = 0,
|
||||||
uint32_t to = UINT32_MAX) const override;
|
uint32_t to = UINT32_MAX) const override;
|
||||||
int getSelectionIndexCount() const override;
|
int getSelectionIndexCount() const override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
int getMouseOverIndex(const QPoint &abs) const override;
|
int getMouseOverIndex(const QPoint &abs) const override;
|
||||||
int getXFromIndex(int index) override;
|
int getXFromIndex(int index) override;
|
||||||
|
@ -110,7 +112,7 @@ public:
|
||||||
const QSize &size, QColor color);
|
const QSize &size, QColor color);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QColor color_;
|
QColor color_;
|
||||||
|
@ -125,7 +127,7 @@ public:
|
||||||
int padding);
|
int padding);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QColor color_;
|
const QColor color_;
|
||||||
|
@ -147,7 +149,7 @@ protected:
|
||||||
void addCopyTextToString(QString &str, uint32_t from = 0,
|
void addCopyTextToString(QString &str, uint32_t from = 0,
|
||||||
uint32_t to = UINT32_MAX) const override;
|
uint32_t to = UINT32_MAX) const override;
|
||||||
int getSelectionIndexCount() const override;
|
int getSelectionIndexCount() const override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
int getMouseOverIndex(const QPoint &abs) const override;
|
int getMouseOverIndex(const QPoint &abs) const override;
|
||||||
int getXFromIndex(int index) override;
|
int getXFromIndex(int index) override;
|
||||||
|
@ -171,7 +173,7 @@ protected:
|
||||||
void addCopyTextToString(QString &str, uint32_t from = 0,
|
void addCopyTextToString(QString &str, uint32_t from = 0,
|
||||||
uint32_t to = UINT32_MAX) const override;
|
uint32_t to = UINT32_MAX) const override;
|
||||||
int getSelectionIndexCount() const override;
|
int getSelectionIndexCount() const override;
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
int getMouseOverIndex(const QPoint &abs) const override;
|
int getMouseOverIndex(const QPoint &abs) const override;
|
||||||
int getXFromIndex(int index) override;
|
int getXFromIndex(int index) override;
|
||||||
|
@ -189,7 +191,7 @@ public:
|
||||||
float radius, float neededMargin);
|
float radius, float neededMargin);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paint(QPainter &painter) override;
|
void paint(QPainter &painter, const MessageColors &messageColors) override;
|
||||||
void paintAnimated(QPainter &painter, int yOffset) override;
|
void paintAnimated(QPainter &painter, int yOffset) override;
|
||||||
int getMouseOverIndex(const QPoint &abs) const override;
|
int getMouseOverIndex(const QPoint &abs) const override;
|
||||||
int getXFromIndex(int index) override;
|
int getXFromIndex(int index) override;
|
||||||
|
|
|
@ -79,8 +79,8 @@ std::unique_ptr<crashpad::CrashpadClient> installCrashHandler()
|
||||||
|
|
||||||
// See https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/handler/crashpad_handler.md
|
// See https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/handler/crashpad_handler.md
|
||||||
// for documentation on available options.
|
// for documentation on available options.
|
||||||
if (!client->StartHandler(handlerPath, databaseDir, {}, {}, {}, {}, true,
|
if (!client->StartHandler(handlerPath, databaseDir, {}, {}, {}, {}, {},
|
||||||
false))
|
true, false))
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoApp) << "Failed to start crashpad handler";
|
qCDebug(chatterinoApp) << "Failed to start crashpad handler";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -27,7 +27,7 @@ void IvrApi::getSubage(QString userName, QString channelName,
|
||||||
})
|
})
|
||||||
.onError([failureCallback](auto result) {
|
.onError([failureCallback](auto result) {
|
||||||
qCWarning(chatterinoIvr)
|
qCWarning(chatterinoIvr)
|
||||||
<< "Failed IVR API Call!" << result.status()
|
<< "Failed IVR API Call!" << result.formatError()
|
||||||
<< QString(result.getData());
|
<< QString(result.getData());
|
||||||
failureCallback();
|
failureCallback();
|
||||||
})
|
})
|
||||||
|
@ -51,7 +51,7 @@ void IvrApi::getBulkEmoteSets(QString emoteSetList,
|
||||||
})
|
})
|
||||||
.onError([failureCallback](auto result) {
|
.onError([failureCallback](auto result) {
|
||||||
qCWarning(chatterinoIvr)
|
qCWarning(chatterinoIvr)
|
||||||
<< "Failed IVR API Call!" << result.status()
|
<< "Failed IVR API Call!" << result.formatError()
|
||||||
<< QString(result.getData());
|
<< QString(result.getData());
|
||||||
failureCallback();
|
failureCallback();
|
||||||
})
|
})
|
||||||
|
|
|
@ -217,17 +217,20 @@ void RecentMessagesApi::loadRecentMessages(const QString &channelName,
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
})
|
||||||
.onError([channelPtr, onError](NetworkResult result) {
|
.onError([channelPtr, onError](const NetworkResult &result) {
|
||||||
auto shared = channelPtr.lock();
|
auto shared = channelPtr.lock();
|
||||||
if (!shared)
|
if (!shared)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
qCDebug(chatterinoRecentMessages)
|
qCDebug(chatterinoRecentMessages)
|
||||||
<< "Failed to load recent messages for" << shared->getName();
|
<< "Failed to load recent messages for" << shared->getName();
|
||||||
|
|
||||||
shared->addMessage(makeSystemMessage(
|
shared->addMessage(makeSystemMessage(
|
||||||
QString("Message history service unavailable (Error %1)")
|
QStringLiteral(
|
||||||
.arg(result.status())));
|
"Message history service unavailable (Error: %1)")
|
||||||
|
.arg(result.formatError())));
|
||||||
|
|
||||||
onError();
|
onError();
|
||||||
})
|
})
|
||||||
|
|
|
@ -193,7 +193,7 @@ void BttvEmotes::loadEmotes()
|
||||||
{
|
{
|
||||||
if (!Settings::instance().enableBTTVGlobalEmotes)
|
if (!Settings::instance().enableBTTVGlobalEmotes)
|
||||||
{
|
{
|
||||||
this->global_.set(EMPTY_EMOTE_MAP);
|
this->setEmotes(EMPTY_EMOTE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,13 +203,18 @@ void BttvEmotes::loadEmotes()
|
||||||
auto emotes = this->global_.get();
|
auto emotes = this->global_.get();
|
||||||
auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes);
|
auto pair = parseGlobalEmotes(result.parseJsonArray(), *emotes);
|
||||||
if (pair.first)
|
if (pair.first)
|
||||||
this->global_.set(
|
this->setEmotes(
|
||||||
std::make_shared<EmoteMap>(std::move(pair.second)));
|
std::make_shared<EmoteMap>(std::move(pair.second)));
|
||||||
return pair.first;
|
return pair.first;
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BttvEmotes::setEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||||
|
{
|
||||||
|
this->global_.set(std::move(emotes));
|
||||||
|
}
|
||||||
|
|
||||||
void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
|
void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
|
||||||
const QString &channelId,
|
const QString &channelId,
|
||||||
const QString &channelDisplayName,
|
const QString &channelDisplayName,
|
||||||
|
@ -254,23 +259,17 @@ void BttvEmotes::loadChannel(std::weak_ptr<Channel> channel,
|
||||||
shared->addMessage(
|
shared->addMessage(
|
||||||
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
||||||
}
|
}
|
||||||
else if (result.status() == NetworkResult::timedoutStatus)
|
|
||||||
{
|
|
||||||
// TODO: Auto retry in case of a timeout, with a delay
|
|
||||||
qCWarning(chatterinoBttv)
|
|
||||||
<< "Fetching BTTV emotes for channel" << channelId
|
|
||||||
<< "failed due to timeout";
|
|
||||||
shared->addMessage(makeSystemMessage(
|
|
||||||
"Failed to fetch BetterTTV channel emotes. (timed out)"));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: Auto retry in case of a timeout, with a delay
|
||||||
|
auto errorString = result.formatError();
|
||||||
qCWarning(chatterinoBttv)
|
qCWarning(chatterinoBttv)
|
||||||
<< "Error fetching BTTV emotes for channel" << channelId
|
<< "Error fetching BTTV emotes for channel" << channelId
|
||||||
<< ", error" << result.status();
|
<< ", error" << errorString;
|
||||||
shared->addMessage(
|
shared->addMessage(makeSystemMessage(
|
||||||
makeSystemMessage("Failed to fetch BetterTTV channel "
|
QStringLiteral("Failed to fetch BetterTTV channel "
|
||||||
"emotes. (unknown error)"));
|
"emotes. (Error: %1)")
|
||||||
|
.arg(errorString)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -29,6 +29,7 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> emotes() const;
|
std::shared_ptr<const EmoteMap> emotes() const;
|
||||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
|
void setEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||||
static void loadChannel(std::weak_ptr<Channel> channel,
|
static void loadChannel(std::weak_ptr<Channel> channel,
|
||||||
const QString &channelId,
|
const QString &channelId,
|
||||||
const QString &channelDisplayName,
|
const QString &channelDisplayName,
|
||||||
|
|
|
@ -265,7 +265,7 @@ void Emojis::loadEmojiSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
const QString &text)
|
const QString &text) const
|
||||||
{
|
{
|
||||||
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
auto result = std::vector<boost::variant<EmotePtr, QString>>();
|
||||||
int lastParsedEmojiEndIndex = 0;
|
int lastParsedEmojiEndIndex = 0;
|
||||||
|
@ -359,7 +359,7 @@ std::vector<boost::variant<EmotePtr, QString>> Emojis::parse(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Emojis::replaceShortCodes(const QString &text)
|
QString Emojis::replaceShortCodes(const QString &text) const
|
||||||
{
|
{
|
||||||
QString ret(text);
|
QString ret(text);
|
||||||
auto it = this->findShortCodesRegex_.globalMatch(text);
|
auto it = this->findShortCodesRegex_.globalMatch(text);
|
||||||
|
@ -393,4 +393,14 @@ QString Emojis::replaceShortCodes(const QString &text)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EmojiMap &Emojis::getEmojis() const
|
||||||
|
{
|
||||||
|
return this->emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<QString> &Emojis::getShortCodes() const
|
||||||
|
{
|
||||||
|
return this->shortCodes;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -37,16 +37,32 @@ struct EmojiData {
|
||||||
|
|
||||||
using EmojiMap = ConcurrentMap<QString, std::shared_ptr<EmojiData>>;
|
using EmojiMap = ConcurrentMap<QString, std::shared_ptr<EmojiData>>;
|
||||||
|
|
||||||
class Emojis
|
class IEmojis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IEmojis() = default;
|
||||||
|
|
||||||
|
virtual std::vector<boost::variant<EmotePtr, QString>> parse(
|
||||||
|
const QString &text) const = 0;
|
||||||
|
virtual const EmojiMap &getEmojis() const = 0;
|
||||||
|
virtual const std::vector<QString> &getShortCodes() const = 0;
|
||||||
|
virtual QString replaceShortCodes(const QString &text) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Emojis : public IEmojis
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void initialize();
|
void initialize();
|
||||||
void load();
|
void load();
|
||||||
std::vector<boost::variant<EmotePtr, QString>> parse(const QString &text);
|
std::vector<boost::variant<EmotePtr, QString>> parse(
|
||||||
|
const QString &text) const override;
|
||||||
|
|
||||||
EmojiMap emojis;
|
EmojiMap emojis;
|
||||||
std::vector<QString> shortCodes;
|
std::vector<QString> shortCodes;
|
||||||
QString replaceShortCodes(const QString &text);
|
QString replaceShortCodes(const QString &text) const override;
|
||||||
|
|
||||||
|
const EmojiMap &getEmojis() const override;
|
||||||
|
const std::vector<QString> &getShortCodes() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadEmojis();
|
void loadEmojis();
|
||||||
|
|
|
@ -188,7 +188,7 @@ void FfzEmotes::loadEmotes()
|
||||||
{
|
{
|
||||||
if (!Settings::instance().enableFFZGlobalEmotes)
|
if (!Settings::instance().enableFFZGlobalEmotes)
|
||||||
{
|
{
|
||||||
this->global_.set(EMPTY_EMOTE_MAP);
|
this->setEmotes(EMPTY_EMOTE_MAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,13 +199,18 @@ void FfzEmotes::loadEmotes()
|
||||||
.timeout(30000)
|
.timeout(30000)
|
||||||
.onSuccess([this](auto result) -> Outcome {
|
.onSuccess([this](auto result) -> Outcome {
|
||||||
auto parsedSet = parseGlobalEmotes(result.parseJson());
|
auto parsedSet = parseGlobalEmotes(result.parseJson());
|
||||||
this->global_.set(std::make_shared<EmoteMap>(std::move(parsedSet)));
|
this->setEmotes(std::make_shared<EmoteMap>(std::move(parsedSet)));
|
||||||
|
|
||||||
return Success;
|
return Success;
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FfzEmotes::setEmotes(std::shared_ptr<const EmoteMap> emotes)
|
||||||
|
{
|
||||||
|
this->global_.set(std::move(emotes));
|
||||||
|
}
|
||||||
|
|
||||||
void FfzEmotes::loadChannel(
|
void FfzEmotes::loadChannel(
|
||||||
std::weak_ptr<Channel> channel, const QString &channelID,
|
std::weak_ptr<Channel> channel, const QString &channelID,
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
@ -268,24 +273,17 @@ void FfzEmotes::loadChannel(
|
||||||
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
makeSystemMessage(CHANNEL_HAS_NO_EMOTES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (result.status() == NetworkResult::timedoutStatus)
|
|
||||||
{
|
|
||||||
// TODO: Auto retry in case of a timeout, with a delay
|
|
||||||
qCWarning(chatterinoFfzemotes)
|
|
||||||
<< "Fetching FFZ emotes for channel" << channelID
|
|
||||||
<< "failed due to timeout";
|
|
||||||
shared->addMessage(
|
|
||||||
makeSystemMessage("Failed to fetch FrankerFaceZ channel "
|
|
||||||
"emotes. (timed out)"));
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: Auto retry in case of a timeout, with a delay
|
||||||
|
auto errorString = result.formatError();
|
||||||
qCWarning(chatterinoFfzemotes)
|
qCWarning(chatterinoFfzemotes)
|
||||||
<< "Error fetching FFZ emotes for channel" << channelID
|
<< "Error fetching FFZ emotes for channel" << channelID
|
||||||
<< ", error" << result.status();
|
<< ", error" << errorString;
|
||||||
shared->addMessage(
|
shared->addMessage(makeSystemMessage(
|
||||||
makeSystemMessage("Failed to fetch FrankerFaceZ channel "
|
QStringLiteral("Failed to fetch FrankerFaceZ channel "
|
||||||
"emotes. (unknown error)"));
|
"emotes. (Error: %1)")
|
||||||
|
.arg(errorString)));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -22,6 +22,7 @@ public:
|
||||||
std::shared_ptr<const EmoteMap> emotes() const;
|
std::shared_ptr<const EmoteMap> emotes() const;
|
||||||
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
boost::optional<EmotePtr> emote(const EmoteName &name) const;
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
|
void setEmotes(std::shared_ptr<const EmoteMap> emotes);
|
||||||
static void loadChannel(
|
static void loadChannel(
|
||||||
std::weak_ptr<Channel> channel, const QString &channelId,
|
std::weak_ptr<Channel> channel, const QString &channelId,
|
||||||
std::function<void(EmoteMap &&)> emoteCallback,
|
std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
|
|
|
@ -64,7 +64,11 @@ MessagePtr IrcMessageBuilder::build()
|
||||||
// message
|
// message
|
||||||
this->addIrcMessageText(this->originalMessage_);
|
this->addIrcMessageText(this->originalMessage_);
|
||||||
|
|
||||||
this->message().searchText = this->message().localizedName + " " +
|
QString stylizedUsername =
|
||||||
|
this->stylizeUsername(this->userName, this->message());
|
||||||
|
|
||||||
|
this->message().searchText = stylizedUsername + " " +
|
||||||
|
this->message().localizedName + " " +
|
||||||
this->userName + ": " + this->originalMessage_;
|
this->userName + ": " + this->originalMessage_;
|
||||||
|
|
||||||
// highlights
|
// highlights
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp" // NOTE: Included to access the mentions channel
|
#include "providers/twitch/TwitchIrcServer.hpp" // NOTE: Included to access the mentions channel
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/IrcHelpers.hpp"
|
#include "util/IrcHelpers.hpp"
|
||||||
#include "util/QObjectRef.hpp"
|
|
||||||
|
|
||||||
#include <QMetaEnum>
|
#include <QMetaEnum>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
@ -151,7 +151,7 @@ void IrcServer::initializeConnection(IrcConnection *connection,
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case IrcAuthType::Pass:
|
case IrcAuthType::Pass:
|
||||||
this->data_->getPassword(
|
this->data_->getPassword(
|
||||||
this, [conn = new QObjectRef(connection) /* can't copy */,
|
this, [conn = new QPointer(connection) /* can't copy */,
|
||||||
this](const QString &password) mutable {
|
this](const QString &password) mutable {
|
||||||
if (*conn)
|
if (*conn)
|
||||||
{
|
{
|
||||||
|
|
|
@ -87,8 +87,11 @@ public:
|
||||||
this->websocketClient_.set_fail_handler([this](auto hdl) {
|
this->websocketClient_.set_fail_handler([this](auto hdl) {
|
||||||
this->onConnectionFail(hdl);
|
this->onConnectionFail(hdl);
|
||||||
});
|
});
|
||||||
this->websocketClient_.set_user_agent("Chatterino/" CHATTERINO_VERSION
|
this->websocketClient_.set_user_agent(
|
||||||
" (" CHATTERINO_GIT_HASH ")");
|
QStringLiteral("Chatterino/%1 (%2)")
|
||||||
|
.arg(Version::instance().version(),
|
||||||
|
Version::instance().commitHash())
|
||||||
|
.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~BasicPubSubManager() = default;
|
virtual ~BasicPubSubManager() = default;
|
||||||
|
|
92
src/providers/seventv/SeventvAPI.cpp
Normal file
92
src/providers/seventv/SeventvAPI.cpp
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#include "providers/seventv/SeventvAPI.hpp"
|
||||||
|
|
||||||
|
#include "common/Literals.hpp"
|
||||||
|
#include "common/NetworkRequest.hpp"
|
||||||
|
#include "common/NetworkResult.hpp"
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using namespace chatterino::literals;
|
||||||
|
|
||||||
|
const QString API_URL_USER = u"https://7tv.io/v3/users/twitch/%1"_s;
|
||||||
|
const QString API_URL_EMOTE_SET = u"https://7tv.io/v3/emote-sets/%1"_s;
|
||||||
|
const QString API_URL_PRESENCES = u"https://7tv.io/v3/users/%1/presences"_s;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// NOLINTBEGIN(readability-convert-member-functions-to-static)
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void SeventvAPI::getUserByTwitchID(
|
||||||
|
const QString &twitchID, SuccessCallback<const QJsonObject &> &&onSuccess,
|
||||||
|
ErrorCallback &&onError)
|
||||||
|
{
|
||||||
|
NetworkRequest(API_URL_USER.arg(twitchID), NetworkRequestType::Get)
|
||||||
|
.timeout(20000)
|
||||||
|
.onSuccess([callback = std::move(onSuccess)](
|
||||||
|
const NetworkResult &result) -> Outcome {
|
||||||
|
auto json = result.parseJson();
|
||||||
|
callback(json);
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([callback = std::move(onError)](const NetworkResult &result) {
|
||||||
|
callback(result);
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SeventvAPI::getEmoteSet(const QString &emoteSet,
|
||||||
|
SuccessCallback<const QJsonObject &> &&onSuccess,
|
||||||
|
ErrorCallback &&onError)
|
||||||
|
{
|
||||||
|
NetworkRequest(API_URL_EMOTE_SET.arg(emoteSet), NetworkRequestType::Get)
|
||||||
|
.timeout(25000)
|
||||||
|
.onSuccess([callback = std::move(onSuccess)](
|
||||||
|
const NetworkResult &result) -> Outcome {
|
||||||
|
auto json = result.parseJson();
|
||||||
|
callback(json);
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([callback = std::move(onError)](const NetworkResult &result) {
|
||||||
|
callback(result);
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SeventvAPI::updatePresence(const QString &twitchChannelID,
|
||||||
|
const QString &seventvUserID,
|
||||||
|
SuccessCallback<> &&onSuccess,
|
||||||
|
ErrorCallback &&onError)
|
||||||
|
{
|
||||||
|
QJsonObject payload{
|
||||||
|
{u"kind"_s, 1}, // UserPresenceKindChannel
|
||||||
|
{u"data"_s,
|
||||||
|
QJsonObject{
|
||||||
|
{u"id"_s, twitchChannelID},
|
||||||
|
{u"platform"_s, u"TWITCH"_s},
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
NetworkRequest(API_URL_PRESENCES.arg(seventvUserID),
|
||||||
|
NetworkRequestType::Post)
|
||||||
|
.json(payload)
|
||||||
|
.timeout(10000)
|
||||||
|
.onSuccess([callback = std::move(onSuccess)](const auto &) -> Outcome {
|
||||||
|
callback();
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([callback = std::move(onError)](const NetworkResult &result) {
|
||||||
|
callback(result);
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
SeventvAPI &getSeventvAPI()
|
||||||
|
{
|
||||||
|
static SeventvAPI instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
|
// NOLINTEND(readability-convert-member-functions-to-static)
|
33
src/providers/seventv/SeventvAPI.hpp
Normal file
33
src/providers/seventv/SeventvAPI.hpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class QString;
|
||||||
|
class QJsonObject;
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class NetworkResult;
|
||||||
|
|
||||||
|
class SeventvAPI
|
||||||
|
{
|
||||||
|
using ErrorCallback = std::function<void(const NetworkResult &)>;
|
||||||
|
template <typename... T>
|
||||||
|
using SuccessCallback = std::function<void(T...)>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void getUserByTwitchID(const QString &twitchID,
|
||||||
|
SuccessCallback<const QJsonObject &> &&onSuccess,
|
||||||
|
ErrorCallback &&onError);
|
||||||
|
void getEmoteSet(const QString &emoteSet,
|
||||||
|
SuccessCallback<const QJsonObject &> &&onSuccess,
|
||||||
|
ErrorCallback &&onError);
|
||||||
|
|
||||||
|
void updatePresence(const QString &twitchChannelID,
|
||||||
|
const QString &seventvUserID,
|
||||||
|
SuccessCallback<> &&onSuccess, ErrorCallback &&onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
SeventvAPI &getSeventvAPI();
|
||||||
|
|
||||||
|
} // namespace chatterino
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue