Merge remote-tracking branch 'main_repo/master' into git_is_pepega
This commit is contained in:
+10
-10
@@ -1,13 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
echo "Running MACDEPLOYQT"
|
echo "Running MACDEPLOYQT"
|
||||||
/usr/local/opt/qt/bin/macdeployqt chatterino.app -dmg
|
/usr/local/opt/qt/bin/macdeployqt chatterino.app
|
||||||
echo "Creating APP folder"
|
echo "Creating python3 virtual environment"
|
||||||
mkdir app
|
python3 -m venv venv
|
||||||
echo "Running hdiutil attach on the built DMG"
|
echo "Entering python3 virtual environment"
|
||||||
hdiutil attach chatterino.dmg
|
. venv/bin/activate
|
||||||
echo "Copying chatterino.app into the app folder"
|
echo "Installing dmgbuild"
|
||||||
cp -r /Volumes/chatterino/chatterino.app app/
|
python3 -m pip install dmgbuild
|
||||||
echo "Creating DMG with create-dmg"
|
echo "Running dmgbuild.."
|
||||||
create-dmg --volname Chatterino2 --volicon ../resources/chatterino.icns --icon-size 50 --app-drop-link 0 0 --format UDBZ chatterino-osx.dmg app/
|
dmgbuild --settings ./../.CI/dmg-settings.py -D app=./chatterino.app Chatterino2 chatterino-osx.dmg
|
||||||
echo "DONE"
|
echo "Done!"
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import biplist
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
#
|
||||||
|
# Example settings file for dmgbuild
|
||||||
|
#
|
||||||
|
|
||||||
|
# Use like this: dmgbuild -s settings.py "Test Volume" test.dmg
|
||||||
|
|
||||||
|
# You can actually use this file for your own application (not just TextEdit)
|
||||||
|
# by doing e.g.
|
||||||
|
#
|
||||||
|
# dmgbuild -s settings.py -D app=/path/to/My.app "My Application" MyApp.dmg
|
||||||
|
|
||||||
|
# .. Useful stuff ..............................................................
|
||||||
|
|
||||||
|
application = defines.get('app', '/Applications/TextEdit.app')
|
||||||
|
appname = os.path.basename(application)
|
||||||
|
|
||||||
|
def icon_from_app(app_path):
|
||||||
|
plist_path = os.path.join(app_path, 'Contents', 'Info.plist')
|
||||||
|
plist = biplist.readPlist(plist_path)
|
||||||
|
icon_name = plist['CFBundleIconFile']
|
||||||
|
icon_root,icon_ext = os.path.splitext(icon_name)
|
||||||
|
if not icon_ext:
|
||||||
|
icon_ext = '.icns'
|
||||||
|
icon_name = icon_root + icon_ext
|
||||||
|
return os.path.join(app_path, 'Contents', 'Resources', icon_name)
|
||||||
|
|
||||||
|
# .. Basics ....................................................................
|
||||||
|
|
||||||
|
# Uncomment to override the output filename
|
||||||
|
# filename = 'test.dmg'
|
||||||
|
|
||||||
|
# Uncomment to override the output volume name
|
||||||
|
# volume_name = 'Test'
|
||||||
|
|
||||||
|
# Volume format (see hdiutil create -help)
|
||||||
|
format = defines.get('format', 'UDBZ')
|
||||||
|
|
||||||
|
# Volume size
|
||||||
|
size = defines.get('size', None)
|
||||||
|
|
||||||
|
# Files to include
|
||||||
|
files = [ application ]
|
||||||
|
|
||||||
|
# Symlinks to create
|
||||||
|
symlinks = { 'Applications': '/Applications' }
|
||||||
|
|
||||||
|
# Volume icon
|
||||||
|
#
|
||||||
|
# You can either define icon, in which case that icon file will be copied to the
|
||||||
|
# image, *or* you can define badge_icon, in which case the icon file you specify
|
||||||
|
# will be used to badge the system's Removable Disk icon
|
||||||
|
#
|
||||||
|
#icon = '/path/to/icon.icns'
|
||||||
|
badge_icon = icon_from_app(application)
|
||||||
|
|
||||||
|
# Where to put the icons
|
||||||
|
icon_locations = {
|
||||||
|
appname: (140, 120),
|
||||||
|
'Applications': (500, 120)
|
||||||
|
}
|
||||||
|
|
||||||
|
# .. Window configuration ......................................................
|
||||||
|
|
||||||
|
# Background
|
||||||
|
#
|
||||||
|
# This is a STRING containing any of the following:
|
||||||
|
#
|
||||||
|
# #3344ff - web-style RGB color
|
||||||
|
# #34f - web-style RGB color, short form (#34f == #3344ff)
|
||||||
|
# rgb(1,0,0) - RGB color, each value is between 0 and 1
|
||||||
|
# hsl(120,1,.5) - HSL (hue saturation lightness) color
|
||||||
|
# hwb(300,0,0) - HWB (hue whiteness blackness) color
|
||||||
|
# cmyk(0,1,0,0) - CMYK color
|
||||||
|
# goldenrod - X11/SVG named color
|
||||||
|
# builtin-arrow - A simple built-in background with a blue arrow
|
||||||
|
# /foo/bar/baz.png - The path to an image file
|
||||||
|
#
|
||||||
|
# The hue component in hsl() and hwb() may include a unit; it defaults to
|
||||||
|
# degrees ('deg'), but also supports radians ('rad') and gradians ('grad'
|
||||||
|
# or 'gon').
|
||||||
|
#
|
||||||
|
# Other color components may be expressed either in the range 0 to 1, or
|
||||||
|
# as percentages (e.g. 60% is equivalent to 0.6).
|
||||||
|
background = 'builtin-arrow'
|
||||||
|
|
||||||
|
show_status_bar = False
|
||||||
|
show_tab_view = False
|
||||||
|
show_toolbar = False
|
||||||
|
show_pathbar = False
|
||||||
|
show_sidebar = False
|
||||||
|
sidebar_width = 180
|
||||||
|
|
||||||
|
# Window position in ((x, y), (w, h)) format
|
||||||
|
window_rect = ((100, 100), (640, 280))
|
||||||
|
|
||||||
|
# Select the default view; must be one of
|
||||||
|
#
|
||||||
|
# 'icon-view'
|
||||||
|
# 'list-view'
|
||||||
|
# 'column-view'
|
||||||
|
# 'coverflow'
|
||||||
|
#
|
||||||
|
default_view = 'icon-view'
|
||||||
|
|
||||||
|
# General view configuration
|
||||||
|
show_icon_preview = False
|
||||||
|
|
||||||
|
# Set these to True to force inclusion of icon/list view settings (otherwise
|
||||||
|
# we only include settings for the default view)
|
||||||
|
include_icon_view_settings = 'auto'
|
||||||
|
include_list_view_settings = 'auto'
|
||||||
|
|
||||||
|
# .. Icon view configuration ...................................................
|
||||||
|
|
||||||
|
arrange_by = None
|
||||||
|
grid_offset = (0, 0)
|
||||||
|
grid_spacing = 100
|
||||||
|
scroll_position = (0, 0)
|
||||||
|
label_pos = 'bottom' # or 'right'
|
||||||
|
text_size = 16
|
||||||
|
icon_size = 128
|
||||||
|
|
||||||
|
# .. List view configuration ...................................................
|
||||||
|
|
||||||
|
# Column names are as follows:
|
||||||
|
#
|
||||||
|
# name
|
||||||
|
# date-modified
|
||||||
|
# date-created
|
||||||
|
# date-added
|
||||||
|
# date-last-opened
|
||||||
|
# size
|
||||||
|
# kind
|
||||||
|
# label
|
||||||
|
# version
|
||||||
|
# comments
|
||||||
|
#
|
||||||
|
list_icon_size = 16
|
||||||
|
list_text_size = 12
|
||||||
|
list_scroll_position = (0, 0)
|
||||||
|
list_sort_by = 'name'
|
||||||
|
list_use_relative_dates = True
|
||||||
|
list_calculate_all_sizes = False,
|
||||||
|
list_columns = ('name', 'date-modified', 'size', 'kind', 'date-added')
|
||||||
|
list_column_widths = {
|
||||||
|
'name': 300,
|
||||||
|
'date-modified': 181,
|
||||||
|
'date-created': 181,
|
||||||
|
'date-added': 181,
|
||||||
|
'date-last-opened': 181,
|
||||||
|
'size': 97,
|
||||||
|
'kind': 115,
|
||||||
|
'label': 100,
|
||||||
|
'version': 75,
|
||||||
|
'comments': 300,
|
||||||
|
}
|
||||||
|
list_column_sort_directions = {
|
||||||
|
'name': 'ascending',
|
||||||
|
'date-modified': 'descending',
|
||||||
|
'date-created': 'descending',
|
||||||
|
'date-added': 'descending',
|
||||||
|
'date-last-opened': 'descending',
|
||||||
|
'size': 'descending',
|
||||||
|
'kind': 'ascending',
|
||||||
|
'label': 'ascending',
|
||||||
|
'version': 'ascending',
|
||||||
|
'comments': 'ascending',
|
||||||
|
}
|
||||||
|
|
||||||
|
# .. License configuration .....................................................
|
||||||
|
|
||||||
|
# Text in the license configuration is stored in the resources, which means
|
||||||
|
# it gets stored in a legacy Mac encoding according to the language. dmgbuild
|
||||||
|
# will *try* to convert Unicode strings to the appropriate encoding, *but*
|
||||||
|
# you should be aware that Python doesn't support all of the necessary encodings;
|
||||||
|
# in many cases you will need to encode the text yourself and use byte strings
|
||||||
|
# instead here.
|
||||||
|
|
||||||
|
# Recognized language names are:
|
||||||
|
#
|
||||||
|
# af_ZA, ar, be_BY, bg_BG, bn, bo, br, ca_ES, cs_CZ, cy, da_DK, de_AT, de_CH,
|
||||||
|
# de_DE, dz_BT, el_CY, el_GR, en_AU, en_CA, en_GB, en_IE, en_SG, en_US, eo,
|
||||||
|
# es_419, es_ES, et_EE, fa_IR, fi_FI, fo_FO, fr_001, fr_BE, fr_CA, fr_CH,
|
||||||
|
# fr_FR, ga-Latg_IE, ga_IE, gd, grc, gu_IN, gv, he_IL, hi_IN, hr_HR, hu_HU,
|
||||||
|
# hy_AM, is_IS, it_CH, it_IT, iu_CA, ja_JP, ka_GE, kl, ko_KR, lt_LT, lv_LV,
|
||||||
|
# mk_MK, mr_IN, mt_MT, nb_NO, ne_NP, nl_BE, nl_NL, nn_NO, pa, pl_PL, pt_BR,
|
||||||
|
# pt_PT, ro_RO, ru_RU, se, sk_SK, sl_SI, sr_RS, sv_SE, th_TH, to_TO, tr_TR,
|
||||||
|
# uk_UA, ur_IN, ur_PK, uz_UZ, vi_VN, zh_CN, zh_TW
|
||||||
|
|
||||||
|
# license = {
|
||||||
|
# 'default-language': 'en_US',
|
||||||
|
# 'licenses': {
|
||||||
|
# # For each language, the text of the license. This can be plain text,
|
||||||
|
# # RTF (in which case it must start "{\rtf1"), or a path to a file
|
||||||
|
# # containing the license text. If you're using RTF,
|
||||||
|
# # watch out for Python escaping (or read it from a file).
|
||||||
|
# 'English': b'''{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
|
||||||
|
# {\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
|
||||||
|
# {\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
|
||||||
|
# {\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
|
||||||
|
# \\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
|
||||||
|
# \\deftab720
|
||||||
|
# \\pard\\pardeftab720\\sa160\\partightenfactor0
|
||||||
|
|
||||||
|
# \\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
|
||||||
|
# \\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
|
||||||
|
# \\pard\\pardeftab720\\sa160\\partightenfactor0
|
||||||
|
|
||||||
|
# \\fs36 \\cf2 \\strokec2 What is this?\\
|
||||||
|
# \\pard\\pardeftab720\\sa160\\partightenfactor0
|
||||||
|
|
||||||
|
# \\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\
|
||||||
|
# \\
|
||||||
|
# }''',
|
||||||
|
# },
|
||||||
|
# 'buttons': {
|
||||||
|
# # For each language, text for the buttons on the licensing window.
|
||||||
|
# #
|
||||||
|
# # Default buttons and text are built-in for the following languages:
|
||||||
|
# #
|
||||||
|
# # English (en_US), German (de_DE), Spanish (es_ES), French (fr_FR),
|
||||||
|
# # Italian (it_IT), Japanese (ja_JP), Dutch (nl_NL), Swedish (sv_SE),
|
||||||
|
# # Brazilian Portuguese (pt_BR), Simplified Chinese (zh_CN),
|
||||||
|
# # Traditional Chinese (zh_TW), Danish (da_DK), Finnish (fi_FI),
|
||||||
|
# # Korean (ko_KR), Norwegian (nb_NO)
|
||||||
|
# #
|
||||||
|
# # You don't need to specify them for those languages; if you fail to
|
||||||
|
# # specify them for some other language, English will be used instead.
|
||||||
|
|
||||||
|
# 'en_US': (
|
||||||
|
# b'English',
|
||||||
|
# b'Agree',
|
||||||
|
# b'Disagree',
|
||||||
|
# b'Print',
|
||||||
|
# b'Save',
|
||||||
|
# b'If you agree with the terms of this license, press "Agree" to '
|
||||||
|
# b'install the software. If you do not agree, press "Disagree".'
|
||||||
|
# ),
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
@@ -25,9 +25,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v2
|
uses: jurplel/install-qt-action@v1
|
||||||
with:
|
|
||||||
modules: qtwebengine
|
|
||||||
|
|
||||||
# WINDOWS
|
# WINDOWS
|
||||||
- name: Cache conan
|
- name: Cache conan
|
||||||
@@ -79,7 +77,7 @@ jobs:
|
|||||||
# LINUX
|
# LINUX
|
||||||
- name: Install dependencies (Ubuntu)
|
- name: Install dependencies (Ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0
|
run: sudo apt-get update && sudo apt-get -y install libssl-dev libboost-dev libboost-system-dev libboost-filesystem-dev libpulse-dev libxkbcommon-x11-0 libgstreamer-plugins-base1.0-0 build-essential libgl1-mesa-dev
|
||||||
|
|
||||||
- name: Build (Ubuntu)
|
- name: Build (Ubuntu)
|
||||||
if: startsWith(matrix.os, 'ubuntu')
|
if: startsWith(matrix.os, 'ubuntu')
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Unversioned
|
||||||
|
- Minor: Emotes in the emote popup are now sorted in the same order as the tab completion (#1549)
|
||||||
|
- Bugfix: Fix preview on hover not working when Animated emotes options was disabled (#1546)
|
||||||
|
- Bugfix: FFZ custom mod badges no longer scale with the emote scale options (#1602)
|
||||||
|
- Settings open faster
|
||||||
|
- Dev: Fully remove Twitch Chatroom support
|
||||||
+15
-17
@@ -131,21 +131,16 @@ SOURCES += \
|
|||||||
src/controllers/commands/CommandController.cpp \
|
src/controllers/commands/CommandController.cpp \
|
||||||
src/controllers/commands/CommandModel.cpp \
|
src/controllers/commands/CommandModel.cpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||||
src/controllers/highlights/HighlightController.cpp \
|
|
||||||
src/controllers/highlights/HighlightModel.cpp \
|
src/controllers/highlights/HighlightModel.cpp \
|
||||||
src/controllers/highlights/HighlightPhrase.cpp \
|
src/controllers/highlights/HighlightPhrase.cpp \
|
||||||
src/controllers/highlights/UserHighlightModel.cpp \
|
src/controllers/highlights/UserHighlightModel.cpp \
|
||||||
src/controllers/ignores/IgnoreController.cpp \
|
|
||||||
src/controllers/ignores/IgnoreModel.cpp \
|
src/controllers/ignores/IgnoreModel.cpp \
|
||||||
src/controllers/moderationactions/ModerationAction.cpp \
|
src/controllers/moderationactions/ModerationAction.cpp \
|
||||||
src/controllers/moderationactions/ModerationActionModel.cpp \
|
src/controllers/moderationactions/ModerationActionModel.cpp \
|
||||||
src/controllers/moderationactions/ModerationActions.cpp \
|
|
||||||
src/controllers/notifications/NotificationController.cpp \
|
src/controllers/notifications/NotificationController.cpp \
|
||||||
src/controllers/notifications/NotificationModel.cpp \
|
src/controllers/notifications/NotificationModel.cpp \
|
||||||
src/controllers/pings/PingController.cpp \
|
src/controllers/pings/MutedChannelModel.cpp \
|
||||||
src/controllers/pings/PingModel.cpp \
|
|
||||||
src/controllers/taggedusers/TaggedUser.cpp \
|
src/controllers/taggedusers/TaggedUser.cpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.cpp \
|
|
||||||
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
src/controllers/taggedusers/TaggedUsersModel.cpp \
|
||||||
src/debug/Benchmark.cpp \
|
src/debug/Benchmark.cpp \
|
||||||
src/main.cpp \
|
src/main.cpp \
|
||||||
@@ -178,15 +173,14 @@ SOURCES += \
|
|||||||
src/providers/irc/IrcConnection2.cpp \
|
src/providers/irc/IrcConnection2.cpp \
|
||||||
src/providers/irc/IrcServer.cpp \
|
src/providers/irc/IrcServer.cpp \
|
||||||
src/providers/LinkResolver.cpp \
|
src/providers/LinkResolver.cpp \
|
||||||
src/providers/twitch/ChatroomChannel.cpp \
|
src/providers/twitch/api/Helix.cpp \
|
||||||
|
src/providers/twitch/api/Kraken.cpp \
|
||||||
src/providers/twitch/IrcMessageHandler.cpp \
|
src/providers/twitch/IrcMessageHandler.cpp \
|
||||||
src/providers/twitch/PartialTwitchUser.cpp \
|
|
||||||
src/providers/twitch/PubsubActions.cpp \
|
src/providers/twitch/PubsubActions.cpp \
|
||||||
src/providers/twitch/PubsubClient.cpp \
|
src/providers/twitch/PubsubClient.cpp \
|
||||||
src/providers/twitch/PubsubHelpers.cpp \
|
src/providers/twitch/PubsubHelpers.cpp \
|
||||||
src/providers/twitch/TwitchAccount.cpp \
|
src/providers/twitch/TwitchAccount.cpp \
|
||||||
src/providers/twitch/TwitchAccountManager.cpp \
|
src/providers/twitch/TwitchAccountManager.cpp \
|
||||||
src/providers/twitch/TwitchApi.cpp \
|
|
||||||
src/providers/twitch/TwitchBadge.cpp \
|
src/providers/twitch/TwitchBadge.cpp \
|
||||||
src/providers/twitch/TwitchBadges.cpp \
|
src/providers/twitch/TwitchBadges.cpp \
|
||||||
src/providers/twitch/TwitchChannel.cpp \
|
src/providers/twitch/TwitchChannel.cpp \
|
||||||
@@ -328,7 +322,6 @@ HEADERS += \
|
|||||||
src/controllers/commands/CommandModel.hpp \
|
src/controllers/commands/CommandModel.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
src/controllers/highlights/HighlightBlacklistModel.hpp \
|
||||||
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
src/controllers/highlights/HighlightBlacklistUser.hpp \
|
||||||
src/controllers/highlights/HighlightController.hpp \
|
|
||||||
src/controllers/highlights/HighlightModel.hpp \
|
src/controllers/highlights/HighlightModel.hpp \
|
||||||
src/controllers/highlights/HighlightPhrase.hpp \
|
src/controllers/highlights/HighlightPhrase.hpp \
|
||||||
src/controllers/highlights/UserHighlightModel.hpp \
|
src/controllers/highlights/UserHighlightModel.hpp \
|
||||||
@@ -337,13 +330,10 @@ HEADERS += \
|
|||||||
src/controllers/ignores/IgnorePhrase.hpp \
|
src/controllers/ignores/IgnorePhrase.hpp \
|
||||||
src/controllers/moderationactions/ModerationAction.hpp \
|
src/controllers/moderationactions/ModerationAction.hpp \
|
||||||
src/controllers/moderationactions/ModerationActionModel.hpp \
|
src/controllers/moderationactions/ModerationActionModel.hpp \
|
||||||
src/controllers/moderationactions/ModerationActions.hpp \
|
|
||||||
src/controllers/notifications/NotificationController.hpp \
|
src/controllers/notifications/NotificationController.hpp \
|
||||||
src/controllers/notifications/NotificationModel.hpp \
|
src/controllers/notifications/NotificationModel.hpp \
|
||||||
src/controllers/pings/PingController.hpp \
|
src/controllers/pings/MutedChannelModel.hpp \
|
||||||
src/controllers/pings/PingModel.hpp \
|
|
||||||
src/controllers/taggedusers/TaggedUser.hpp \
|
src/controllers/taggedusers/TaggedUser.hpp \
|
||||||
src/controllers/taggedusers/TaggedUsersController.hpp \
|
|
||||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||||
src/debug/AssertInGuiThread.hpp \
|
src/debug/AssertInGuiThread.hpp \
|
||||||
src/debug/Benchmark.hpp \
|
src/debug/Benchmark.hpp \
|
||||||
@@ -383,16 +373,15 @@ HEADERS += \
|
|||||||
src/providers/irc/IrcConnection2.hpp \
|
src/providers/irc/IrcConnection2.hpp \
|
||||||
src/providers/irc/IrcServer.hpp \
|
src/providers/irc/IrcServer.hpp \
|
||||||
src/providers/LinkResolver.hpp \
|
src/providers/LinkResolver.hpp \
|
||||||
src/providers/twitch/ChatroomChannel.hpp \
|
src/providers/twitch/api/Helix.hpp \
|
||||||
|
src/providers/twitch/api/Kraken.hpp \
|
||||||
src/providers/twitch/EmoteValue.hpp \
|
src/providers/twitch/EmoteValue.hpp \
|
||||||
src/providers/twitch/IrcMessageHandler.hpp \
|
src/providers/twitch/IrcMessageHandler.hpp \
|
||||||
src/providers/twitch/PartialTwitchUser.hpp \
|
|
||||||
src/providers/twitch/PubsubActions.hpp \
|
src/providers/twitch/PubsubActions.hpp \
|
||||||
src/providers/twitch/PubsubClient.hpp \
|
src/providers/twitch/PubsubClient.hpp \
|
||||||
src/providers/twitch/PubsubHelpers.hpp \
|
src/providers/twitch/PubsubHelpers.hpp \
|
||||||
src/providers/twitch/TwitchAccount.hpp \
|
src/providers/twitch/TwitchAccount.hpp \
|
||||||
src/providers/twitch/TwitchAccountManager.hpp \
|
src/providers/twitch/TwitchAccountManager.hpp \
|
||||||
src/providers/twitch/TwitchApi.hpp \
|
|
||||||
src/providers/twitch/TwitchBadge.hpp \
|
src/providers/twitch/TwitchBadge.hpp \
|
||||||
src/providers/twitch/TwitchBadges.hpp \
|
src/providers/twitch/TwitchBadges.hpp \
|
||||||
src/providers/twitch/TwitchChannel.hpp \
|
src/providers/twitch/TwitchChannel.hpp \
|
||||||
@@ -437,6 +426,7 @@ HEADERS += \
|
|||||||
src/util/LayoutCreator.hpp \
|
src/util/LayoutCreator.hpp \
|
||||||
src/util/LayoutHelper.hpp \
|
src/util/LayoutHelper.hpp \
|
||||||
src/util/Overloaded.hpp \
|
src/util/Overloaded.hpp \
|
||||||
|
src/util/PersistSignalVector.hpp \
|
||||||
src/util/PostToThread.hpp \
|
src/util/PostToThread.hpp \
|
||||||
src/util/QObjectRef.hpp \
|
src/util/QObjectRef.hpp \
|
||||||
src/util/QStringHash.hpp \
|
src/util/QStringHash.hpp \
|
||||||
@@ -564,3 +554,11 @@ git_hash = $$str_member($$git_commit, 0, 8)
|
|||||||
DEFINES += CHATTERINO_GIT_COMMIT=\\\"$$git_commit\\\"
|
DEFINES += CHATTERINO_GIT_COMMIT=\\\"$$git_commit\\\"
|
||||||
DEFINES += CHATTERINO_GIT_RELEASE=\\\"$$git_release\\\"
|
DEFINES += CHATTERINO_GIT_RELEASE=\\\"$$git_release\\\"
|
||||||
DEFINES += CHATTERINO_GIT_HASH=\\\"$$git_hash\\\"
|
DEFINES += CHATTERINO_GIT_HASH=\\\"$$git_hash\\\"
|
||||||
|
|
||||||
|
CONFIG(debug, debug|release) {
|
||||||
|
message("Building Chatterino2 DEBUG")
|
||||||
|
} else {
|
||||||
|
message("Building Chatterino2 RELEASE")
|
||||||
|
}
|
||||||
|
|
||||||
|
message("Injected git values: $$git_commit ($$git_release) $$git_hash")
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ TranRed | https://github.com/TranRed | | Contributor
|
|||||||
RAnders00 | https://github.com/RAnders00 | | Contributor
|
RAnders00 | https://github.com/RAnders00 | | Contributor
|
||||||
YungLPR | https://github.com/leon-richardt | | Contributor
|
YungLPR | https://github.com/leon-richardt | | Contributor
|
||||||
Mm2PL | https://github.com/mm2pl | | Contributor
|
Mm2PL | https://github.com/mm2pl | | Contributor
|
||||||
|
gempir | https://github.com/gempir | | Contributor
|
||||||
# If you are a contributor add yourself above this line
|
# If you are a contributor add yourself above this line
|
||||||
|
|
||||||
Defman21 | https://github.com/Defman21 | | Documentation
|
Defman21 | https://github.com/Defman21 | | Documentation
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ chatterino--TitleLabel {
|
|||||||
font-family: "Segoe UI light";
|
font-family: "Segoe UI light";
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #4FC3F7;
|
color: #4FC3F7;
|
||||||
margin-top: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chatterino--DescriptionLabel {
|
chatterino--DescriptionLabel {
|
||||||
|
|||||||
@@ -28,8 +28,9 @@
|
|||||||
<file>buttons/unmod.png</file>
|
<file>buttons/unmod.png</file>
|
||||||
<file>buttons/update.png</file>
|
<file>buttons/update.png</file>
|
||||||
<file>buttons/updateError.png</file>
|
<file>buttons/updateError.png</file>
|
||||||
<file>com.chatterino.chatterino.desktop</file>
|
|
||||||
<file>chatterino.icns</file>
|
<file>chatterino.icns</file>
|
||||||
|
<file>com.chatterino.chatterino.appdata.xml</file>
|
||||||
|
<file>com.chatterino.chatterino.desktop</file>
|
||||||
<file>contributors.txt</file>
|
<file>contributors.txt</file>
|
||||||
<file>emoji.json</file>
|
<file>emoji.json</file>
|
||||||
<file>emojidata.txt</file>
|
<file>emojidata.txt</file>
|
||||||
@@ -50,6 +51,12 @@
|
|||||||
<file>licenses/websocketpp.txt</file>
|
<file>licenses/websocketpp.txt</file>
|
||||||
<file>pajaDank.png</file>
|
<file>pajaDank.png</file>
|
||||||
<file>qss/settings.qss</file>
|
<file>qss/settings.qss</file>
|
||||||
|
<file>scrolling/downScroll.png</file>
|
||||||
|
<file>scrolling/downScroll.svg</file>
|
||||||
|
<file>scrolling/neutralScroll.png</file>
|
||||||
|
<file>scrolling/neutralScroll.svg</file>
|
||||||
|
<file>scrolling/upScroll.png</file>
|
||||||
|
<file>scrolling/upScroll.svg</file>
|
||||||
<file>settings/about.svg</file>
|
<file>settings/about.svg</file>
|
||||||
<file>settings/aboutlogo.png</file>
|
<file>settings/aboutlogo.png</file>
|
||||||
<file>settings/accounts.svg</file>
|
<file>settings/accounts.svg</file>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 8.4666665 8.4666669"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||||
|
sodipodi:docname="downScroll.svg"
|
||||||
|
inkscape:export-filename="/home/leon/Projects/Chatterino/chatterino2/resources/scrolling/neutralScroll.png"
|
||||||
|
inkscape:export-xdpi="7.6051788"
|
||||||
|
inkscape:export-ydpi="7.6051788">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="14.417768"
|
||||||
|
inkscape:cy="15.865661"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1053"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
units="px"
|
||||||
|
inkscape:pagecheckerboard="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4532" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-288.53332)">
|
||||||
|
<circle
|
||||||
|
style="fill:#f6f6f6;fill-opacity:1;stroke:#000000;stroke-width:0.03761128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3719"
|
||||||
|
cx="4.2333331"
|
||||||
|
cy="292.76666"
|
||||||
|
r="4.2145276" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.05049507;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4526"
|
||||||
|
cx="4.2333317"
|
||||||
|
cy="292.76425"
|
||||||
|
r="0.72627902" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56499994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4538"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="99.21875"
|
||||||
|
sodipodi:cy="155.44792"
|
||||||
|
sodipodi:r1="13.229166"
|
||||||
|
sodipodi:r2="6.614583"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 112.44792,155.44792 -19.843753,11.4568 v -22.91359 z"
|
||||||
|
inkscape:transform-center-x="5.0649804e-06"
|
||||||
|
inkscape:transform-center-y="2.2264812"
|
||||||
|
transform="matrix(0,0.09239709,-0.09239709,0,18.596269,286.19867)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 8.4666665 8.4666669"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||||
|
sodipodi:docname="neutralScroll.svg"
|
||||||
|
inkscape:export-filename="/home/leon/Projects/Chatterino/chatterino2/resources/scrolling/neutralScroll.png"
|
||||||
|
inkscape:export-xdpi="7.6051788"
|
||||||
|
inkscape:export-ydpi="7.6051788">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="25.340042"
|
||||||
|
inkscape:cy="15.865661"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1053"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
units="px"
|
||||||
|
inkscape:pagecheckerboard="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4532" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-288.53332)">
|
||||||
|
<g
|
||||||
|
id="g5271"
|
||||||
|
transform="matrix(0.07922061,0,0,0.07922061,-4.1782222,281.17363)">
|
||||||
|
<circle
|
||||||
|
r="53.199886"
|
||||||
|
cy="146.33852"
|
||||||
|
cx="106.17888"
|
||||||
|
id="path3719"
|
||||||
|
style="fill:#f6f6f6;fill-opacity:1;stroke:#000000;stroke-width:0.47476631;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
<g
|
||||||
|
transform="matrix(1.7324984,0,0,1.7324984,-77.775866,-107.19273)"
|
||||||
|
id="g4665">
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.36790693;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4526"
|
||||||
|
cx="106.17887"
|
||||||
|
cy="146.32092"
|
||||||
|
r="5.2916665" />
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56499994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4538"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
sodipodi:cx="99.21875"
|
||||||
|
sodipodi:cy="155.44792"
|
||||||
|
sodipodi:r1="13.229166"
|
||||||
|
sodipodi:r2="6.614583"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m 112.44792,155.44792 -19.843753,11.4568 v -22.91359 z"
|
||||||
|
inkscape:transform-center-x="5.0649804e-06"
|
||||||
|
inkscape:transform-center-y="2.2264812"
|
||||||
|
transform="matrix(0,0.67320493,-0.67320493,0,210.82719,98.484167)" />
|
||||||
|
<path
|
||||||
|
transform="matrix(0,-0.67320493,-0.67320493,0,210.82719,194.19287)"
|
||||||
|
inkscape:transform-center-y="-2.2264854"
|
||||||
|
inkscape:transform-center-x="5.0649804e-06"
|
||||||
|
d="m 112.44792,155.44792 -19.843753,11.4568 v -22.91359 z"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:r2="6.614583"
|
||||||
|
sodipodi:r1="13.229166"
|
||||||
|
sodipodi:cy="155.44792"
|
||||||
|
sodipodi:cx="99.21875"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
id="path4540"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56499994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
sodipodi:type="star" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 8.4666665 8.4666669"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||||
|
sodipodi:docname="upScroll.svg"
|
||||||
|
inkscape:export-filename="/home/leon/Projects/Chatterino/chatterino2/resources/scrolling/neutralScroll.png"
|
||||||
|
inkscape:export-xdpi="7.6051788"
|
||||||
|
inkscape:export-ydpi="7.6051788">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="14.417768"
|
||||||
|
inkscape:cy="15.865661"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1053"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
units="px"
|
||||||
|
inkscape:pagecheckerboard="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4532" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-288.53332)">
|
||||||
|
<circle
|
||||||
|
style="fill:#f6f6f6;fill-opacity:1;stroke:#000000;stroke-width:0.03761128;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path3719"
|
||||||
|
cx="4.2333331"
|
||||||
|
cy="292.76666"
|
||||||
|
r="4.2145276" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.05049507;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4526"
|
||||||
|
cx="4.2333317"
|
||||||
|
cy="292.76425"
|
||||||
|
r="0.72627902" />
|
||||||
|
<path
|
||||||
|
transform="matrix(0,-0.09239709,-0.09239709,0,18.596269,299.33465)"
|
||||||
|
inkscape:transform-center-y="-2.2264854"
|
||||||
|
inkscape:transform-center-x="5.0649804e-06"
|
||||||
|
d="m 112.44792,155.44792 -19.843753,11.4568 v -22.91359 z"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:flatsided="true"
|
||||||
|
sodipodi:arg2="1.0471976"
|
||||||
|
sodipodi:arg1="0"
|
||||||
|
sodipodi:r2="6.614583"
|
||||||
|
sodipodi:r1="13.229166"
|
||||||
|
sodipodi:cy="155.44792"
|
||||||
|
sodipodi:cx="99.21875"
|
||||||
|
sodipodi:sides="3"
|
||||||
|
id="path4540"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.56499994;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
sodipodi:type="star" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
+7
-21
@@ -5,12 +5,8 @@
|
|||||||
#include "common/Args.hpp"
|
#include "common/Args.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "controllers/pings/PingController.hpp"
|
|
||||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
@@ -54,16 +50,10 @@ Application::Application(Settings &_settings, Paths &_paths)
|
|||||||
|
|
||||||
, accounts(&this->emplace<AccountController>())
|
, accounts(&this->emplace<AccountController>())
|
||||||
, commands(&this->emplace<CommandController>())
|
, commands(&this->emplace<CommandController>())
|
||||||
, highlights(&this->emplace<HighlightController>())
|
|
||||||
, notifications(&this->emplace<NotificationController>())
|
, notifications(&this->emplace<NotificationController>())
|
||||||
, pings(&this->emplace<PingController>())
|
|
||||||
, ignores(&this->emplace<IgnoreController>())
|
|
||||||
, taggedUsers(&this->emplace<TaggedUsersController>())
|
|
||||||
, moderationActions(&this->emplace<ModerationActions>())
|
|
||||||
, twitch2(&this->emplace<TwitchIrcServer>())
|
, twitch2(&this->emplace<TwitchIrcServer>())
|
||||||
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
, chatterinoBadges(&this->emplace<ChatterinoBadges>())
|
||||||
, logging(&this->emplace<Logging>())
|
, logging(&this->emplace<Logging>())
|
||||||
|
|
||||||
{
|
{
|
||||||
this->instance = this;
|
this->instance = this;
|
||||||
|
|
||||||
@@ -115,9 +105,6 @@ void Application::initialize(Settings &settings, Paths &paths)
|
|||||||
|
|
||||||
this->initNm(paths);
|
this->initNm(paths);
|
||||||
this->initPubsub();
|
this->initPubsub();
|
||||||
|
|
||||||
this->moderationActions->items.delayedItemsChanged.connect(
|
|
||||||
[this] { this->windows->forceLayoutChannelViews(); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Application::run(QApplication &qtApp)
|
int Application::run(QApplication &qtApp)
|
||||||
@@ -130,6 +117,13 @@ int Application::run(QApplication &qtApp)
|
|||||||
|
|
||||||
getSettings()->betaUpdates.connect(
|
getSettings()->betaUpdates.connect(
|
||||||
[] { Updates::instance().checkForUpdates(); }, false);
|
[] { Updates::instance().checkForUpdates(); }, false);
|
||||||
|
getSettings()->moderationActions.delayedItemsChanged.connect(
|
||||||
|
[this] { this->windows->forceLayoutChannelViews(); });
|
||||||
|
|
||||||
|
getSettings()->highlightedMessages.delayedItemsChanged.connect(
|
||||||
|
[this] { this->windows->forceLayoutChannelViews(); });
|
||||||
|
getSettings()->highlightedUsers.delayedItemsChanged.connect(
|
||||||
|
[this] { this->windows->forceLayoutChannelViews(); });
|
||||||
|
|
||||||
return qtApp.exec();
|
return qtApp.exec();
|
||||||
}
|
}
|
||||||
@@ -156,14 +150,6 @@ void Application::initNm(Paths &paths)
|
|||||||
|
|
||||||
void Application::initPubsub()
|
void Application::initPubsub()
|
||||||
{
|
{
|
||||||
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
|
|
||||||
qDebug() << "WHISPER SENT LOL"; //
|
|
||||||
});
|
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
|
|
||||||
qDebug() << "WHISPER RECEIVED LOL"; //
|
|
||||||
});
|
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
||||||
[this](const auto &action) {
|
[this](const auto &action) {
|
||||||
auto chan =
|
auto chan =
|
||||||
|
|||||||
+1
-10
@@ -3,6 +3,7 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/SignalVector.hpp"
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
#include "singletons/NativeMessaging.hpp"
|
#include "singletons/NativeMessaging.hpp"
|
||||||
|
|
||||||
@@ -12,13 +13,8 @@ class TwitchIrcServer;
|
|||||||
class PubSub;
|
class PubSub;
|
||||||
|
|
||||||
class CommandController;
|
class CommandController;
|
||||||
class HighlightController;
|
|
||||||
class IgnoreController;
|
|
||||||
class TaggedUsersController;
|
|
||||||
class AccountController;
|
class AccountController;
|
||||||
class ModerationActions;
|
|
||||||
class NotificationController;
|
class NotificationController;
|
||||||
class PingController;
|
|
||||||
|
|
||||||
class Theme;
|
class Theme;
|
||||||
class WindowManager;
|
class WindowManager;
|
||||||
@@ -58,12 +54,7 @@ public:
|
|||||||
|
|
||||||
AccountController *const accounts{};
|
AccountController *const accounts{};
|
||||||
CommandController *const commands{};
|
CommandController *const commands{};
|
||||||
HighlightController *const highlights{};
|
|
||||||
NotificationController *const notifications{};
|
NotificationController *const notifications{};
|
||||||
PingController *const pings{};
|
|
||||||
IgnoreController *const ignores{};
|
|
||||||
TaggedUsersController *const taggedUsers{};
|
|
||||||
ModerationActions *const moderationActions{};
|
|
||||||
TwitchIrcServer *const twitch2{};
|
TwitchIrcServer *const twitch2{};
|
||||||
ChatterinoBadges *const chatterinoBadges{};
|
ChatterinoBadges *const chatterinoBadges{};
|
||||||
|
|
||||||
|
|||||||
@@ -159,21 +159,6 @@ void AB_THEME_CLASS::actuallyUpdate(double hue, double multiplier)
|
|||||||
this->messages.backgrounds.regular = getColor(0, sat, 1);
|
this->messages.backgrounds.regular = getColor(0, sat, 1);
|
||||||
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
|
this->messages.backgrounds.alternate = getColor(0, sat, 0.96);
|
||||||
|
|
||||||
if (isLight_)
|
|
||||||
{
|
|
||||||
this->messages.backgrounds.highlighted =
|
|
||||||
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// REMOVED
|
|
||||||
// this->messages.backgrounds.highlighted =
|
|
||||||
// QColor(getSettings()->highlightColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->messages.backgrounds.subscription =
|
|
||||||
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
|
|
||||||
|
|
||||||
// this->messages.backgrounds.resub
|
// this->messages.backgrounds.resub
|
||||||
// this->messages.backgrounds.whisper
|
// this->messages.backgrounds.whisper
|
||||||
this->messages.disabled = getColor(0, sat, 1, 0.6);
|
this->messages.disabled = getColor(0, sat, 1, 0.6);
|
||||||
|
|||||||
@@ -64,8 +64,6 @@ public:
|
|||||||
struct {
|
struct {
|
||||||
QColor regular;
|
QColor regular;
|
||||||
QColor alternate;
|
QColor alternate;
|
||||||
QColor highlighted;
|
|
||||||
QColor subscription;
|
|
||||||
// QColor whisper;
|
// QColor whisper;
|
||||||
} backgrounds;
|
} backgrounds;
|
||||||
|
|
||||||
|
|||||||
@@ -154,11 +154,6 @@ namespace {
|
|||||||
toBeRemoved << info.absoluteFilePath();
|
toBeRemoved << info.absoluteFilePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &&path : toBeRemoved)
|
|
||||||
{
|
|
||||||
qDebug() << path << QFile(path).remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ Resources2::Resources2()
|
|||||||
this->error = QPixmap(":/error.png");
|
this->error = QPixmap(":/error.png");
|
||||||
this->icon = QPixmap(":/icon.png");
|
this->icon = QPixmap(":/icon.png");
|
||||||
this->pajaDank = QPixmap(":/pajaDank.png");
|
this->pajaDank = QPixmap(":/pajaDank.png");
|
||||||
|
this->scrolling.downScroll = QPixmap(":/scrolling/downScroll.png");
|
||||||
|
this->scrolling.neutralScroll = QPixmap(":/scrolling/neutralScroll.png");
|
||||||
|
this->scrolling.upScroll = QPixmap(":/scrolling/upScroll.png");
|
||||||
this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png");
|
this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png");
|
||||||
this->split.down = QPixmap(":/split/down.png");
|
this->split.down = QPixmap(":/split/down.png");
|
||||||
this->split.left = QPixmap(":/split/left.png");
|
this->split.left = QPixmap(":/split/left.png");
|
||||||
@@ -50,4 +53,4 @@ Resources2::Resources2()
|
|||||||
this->twitch.vip = QPixmap(":/twitch/vip.png");
|
this->twitch.vip = QPixmap(":/twitch/vip.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
@@ -39,6 +38,11 @@ public:
|
|||||||
QPixmap error;
|
QPixmap error;
|
||||||
QPixmap icon;
|
QPixmap icon;
|
||||||
QPixmap pajaDank;
|
QPixmap pajaDank;
|
||||||
|
struct {
|
||||||
|
QPixmap downScroll;
|
||||||
|
QPixmap neutralScroll;
|
||||||
|
QPixmap upScroll;
|
||||||
|
} scrolling;
|
||||||
struct {
|
struct {
|
||||||
QPixmap aboutlogo;
|
QPixmap aboutlogo;
|
||||||
} settings;
|
} settings;
|
||||||
@@ -65,4 +69,4 @@ public:
|
|||||||
} twitch;
|
} twitch;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
@@ -38,13 +38,7 @@ bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
|||||||
return this->isEmote();
|
return this->isEmote();
|
||||||
}
|
}
|
||||||
|
|
||||||
// try comparing insensitively, if they are the same then senstively
|
return CompletionModel::compareStrings(this->string, that.string);
|
||||||
// (fixes order of LuL and LUL)
|
|
||||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
|
||||||
if (k == 0)
|
|
||||||
return this->string > that.string;
|
|
||||||
|
|
||||||
return k < 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -133,7 +127,7 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
TaggedString::Type::Username);
|
TaggedString::Type::Username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (!getSettings()->userCompletionOnlyWithAt)
|
||||||
{
|
{
|
||||||
for (const auto &name :
|
for (const auto &name :
|
||||||
usernames->subrange(Prefix(usernamePrefix)))
|
usernames->subrange(Prefix(usernamePrefix)))
|
||||||
@@ -191,4 +185,15 @@ void CompletionModel::refresh(const QString &prefix, bool isFirstWord)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CompletionModel::compareStrings(const QString &a, const QString &b)
|
||||||
|
{
|
||||||
|
// try comparing insensitively, if they are the same then senstively
|
||||||
|
// (fixes order of LuL and LUL)
|
||||||
|
int k = QString::compare(a, b, Qt::CaseInsensitive);
|
||||||
|
if (k == 0)
|
||||||
|
return a > b;
|
||||||
|
|
||||||
|
return k < 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ public:
|
|||||||
|
|
||||||
void refresh(const QString &prefix, bool isFirstWord = false);
|
void refresh(const QString &prefix, bool isFirstWord = false);
|
||||||
|
|
||||||
|
static bool compareStrings(const QString &a, const QString &b);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TaggedString createUser(const QString &str);
|
TaggedString createUser(const QString &str);
|
||||||
|
|
||||||
|
|||||||
+118
-191
@@ -4,244 +4,171 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <shared_mutex>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename TVectorItem>
|
template <typename T>
|
||||||
struct SignalVectorItemArgs {
|
struct SignalVectorItemEvent {
|
||||||
const TVectorItem &item;
|
const T &item;
|
||||||
int index;
|
int index;
|
||||||
void *caller;
|
void *caller;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TVectorItem>
|
template <typename T>
|
||||||
class ReadOnlySignalVector : boost::noncopyable
|
class SignalVector : boost::noncopyable
|
||||||
{
|
{
|
||||||
using VecIt = typename std::vector<TVectorItem>::iterator;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct Iterator
|
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemInserted;
|
||||||
: public std::iterator<std::input_iterator_tag, TVectorItem> {
|
pajlada::Signals::Signal<SignalVectorItemEvent<T>> itemRemoved;
|
||||||
Iterator(VecIt &&it, std::shared_mutex &mutex)
|
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||||
: it_(std::move(it))
|
|
||||||
, lock_(mutex)
|
|
||||||
, mutex_(mutex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator(const Iterator &other)
|
SignalVector()
|
||||||
: it_(other.it_)
|
: readOnly_(new std::vector<T>())
|
||||||
, lock_(other.mutex_)
|
|
||||||
, mutex_(other.mutex_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator &operator=(const Iterator &other)
|
|
||||||
{
|
|
||||||
this->lock_ = std::shared_lock(other.mutex_.get());
|
|
||||||
this->mutex_ = other.mutex_;
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
TVectorItem &operator*()
|
|
||||||
{
|
|
||||||
return it_.operator*();
|
|
||||||
}
|
|
||||||
|
|
||||||
Iterator &operator++()
|
|
||||||
{
|
|
||||||
++this->it_;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const Iterator &other)
|
|
||||||
{
|
|
||||||
return this->it_ == other.it_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator!=(const Iterator &other)
|
|
||||||
{
|
|
||||||
return this->it_ != other.it_;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator-(const Iterator &other)
|
|
||||||
{
|
|
||||||
return this->it_ - other.it_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
VecIt it_;
|
|
||||||
std::shared_lock<std::shared_mutex> lock_;
|
|
||||||
std::reference_wrapper<std::shared_mutex> mutex_;
|
|
||||||
};
|
|
||||||
|
|
||||||
ReadOnlySignalVector()
|
|
||||||
{
|
{
|
||||||
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
|
||||||
[this] { this->delayedItemsChanged.invoke(); });
|
[this] { this->delayedItemsChanged.invoke(); });
|
||||||
this->itemsChangedTimer_.setInterval(100);
|
this->itemsChangedTimer_.setInterval(100);
|
||||||
this->itemsChangedTimer_.setSingleShot(true);
|
this->itemsChangedTimer_.setSingleShot(true);
|
||||||
}
|
}
|
||||||
virtual ~ReadOnlySignalVector() = default;
|
|
||||||
|
|
||||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
SignalVector(std::function<bool(const T &, const T &)> &&compare)
|
||||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
: SignalVector()
|
||||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
|
||||||
|
|
||||||
Iterator begin() const
|
|
||||||
{
|
{
|
||||||
return Iterator(
|
itemCompare_ = std::move(compare);
|
||||||
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
|
|
||||||
this->mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator end() const
|
virtual bool isSorted() const
|
||||||
{
|
{
|
||||||
return Iterator(
|
return bool(this->itemCompare_);
|
||||||
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
|
|
||||||
this->mutex_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
/// A read-only version of the vector which can be used concurrently.
|
||||||
|
std::shared_ptr<const std::vector<T>> readOnly()
|
||||||
{
|
{
|
||||||
std::shared_lock lock(this->mutex_);
|
return this->readOnly_;
|
||||||
|
|
||||||
return this->vector_.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<TVectorItem> &getVector() const
|
/// This may only be called from the GUI thread.
|
||||||
|
///
|
||||||
|
/// @param item
|
||||||
|
/// Item to be inserted.
|
||||||
|
/// @param proposedIndex
|
||||||
|
/// Index to insert at. `-1` will append at the end.
|
||||||
|
/// Will be ignored if the vector is sorted.
|
||||||
|
/// @param caller
|
||||||
|
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||||
|
/// signals.
|
||||||
|
int insert(const T &item, int index = -1, void *caller = nullptr)
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
return this->vector_;
|
if (this->isSorted())
|
||||||
|
{
|
||||||
|
auto it = std::lower_bound(this->items_.begin(), this->items_.end(),
|
||||||
|
item, this->itemCompare_);
|
||||||
|
index = it - this->items_.begin();
|
||||||
|
this->items_.insert(it, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (index == -1)
|
||||||
|
index = this->items_.size();
|
||||||
|
else
|
||||||
|
assert(index >= 0 && index <= this->items_.size());
|
||||||
|
|
||||||
|
this->items_.insert(this->items_.begin() + index, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalVectorItemEvent<T> args{item, index, caller};
|
||||||
|
this->itemInserted.invoke(args);
|
||||||
|
this->itemsChanged_();
|
||||||
|
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TVectorItem> cloneVector() const
|
/// This may only be called from the GUI thread.
|
||||||
|
///
|
||||||
|
/// @param item
|
||||||
|
/// Item to be appended.
|
||||||
|
/// @param caller
|
||||||
|
/// Caller id which will be passed in the itemInserted and itemRemoved
|
||||||
|
/// signals.
|
||||||
|
int append(const T &item, void *caller = nullptr)
|
||||||
{
|
{
|
||||||
std::shared_lock lock(this->mutex_);
|
return this->insert(item, -1, caller);
|
||||||
|
|
||||||
return this->vector_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void invokeDelayedItemsChanged()
|
void removeAt(int index, void *caller = nullptr)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
assert(index >= 0 && index < int(this->items_.size()));
|
||||||
|
|
||||||
|
T item = this->items_[index];
|
||||||
|
this->items_.erase(this->items_.begin() + index);
|
||||||
|
|
||||||
|
SignalVectorItemEvent<T> args{item, index, caller};
|
||||||
|
this->itemRemoved.invoke(args);
|
||||||
|
|
||||||
|
this->itemsChanged_();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<T> &raw() const
|
||||||
{
|
{
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
|
|
||||||
|
return this->items_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[deprecated]] std::vector<T> cloneVector()
|
||||||
|
{
|
||||||
|
return *this->readOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
// mirror vector functions
|
||||||
|
auto begin() const
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto end() const
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
decltype(auto) operator[](size_t index)
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto empty()
|
||||||
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
return this->items_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void itemsChanged_()
|
||||||
|
{
|
||||||
|
// emit delayed event
|
||||||
if (!this->itemsChangedTimer_.isActive())
|
if (!this->itemsChangedTimer_.isActive())
|
||||||
{
|
{
|
||||||
this->itemsChangedTimer_.start();
|
this->itemsChangedTimer_.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update concurrent version
|
||||||
|
this->readOnly_ = std::make_shared<const std::vector<T>>(this->items_);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool isSorted() const = 0;
|
std::vector<T> items_;
|
||||||
|
std::shared_ptr<const std::vector<T>> readOnly_;
|
||||||
protected:
|
|
||||||
std::vector<TVectorItem> vector_;
|
|
||||||
QTimer itemsChangedTimer_;
|
QTimer itemsChangedTimer_;
|
||||||
mutable std::shared_mutex mutex_;
|
std::function<bool(const T &, const T &)> itemCompare_;
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TVectorItem>
|
|
||||||
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// returns the actual index of the inserted item
|
|
||||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
|
||||||
void *caller = nullptr) = 0;
|
|
||||||
|
|
||||||
void removeItem(int index, void *caller = nullptr)
|
|
||||||
{
|
|
||||||
assertInGuiThread();
|
|
||||||
std::unique_lock lock(this->mutex_);
|
|
||||||
|
|
||||||
assert(index >= 0 && index < int(this->vector_.size()));
|
|
||||||
|
|
||||||
TVectorItem item = this->vector_[index];
|
|
||||||
|
|
||||||
this->vector_.erase(this->vector_.begin() + index);
|
|
||||||
lock.unlock(); // manual unlock
|
|
||||||
|
|
||||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
|
||||||
this->itemRemoved.invoke(args);
|
|
||||||
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
int appendItem(const TVectorItem &item, void *caller = nullptr)
|
|
||||||
{
|
|
||||||
return this->insertItem(item, -1, caller);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TVectorItem>
|
|
||||||
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int insertItem(const TVectorItem &item, int index = -1,
|
|
||||||
void *caller = nullptr) override
|
|
||||||
{
|
|
||||||
assertInGuiThread();
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock lock(this->mutex_);
|
|
||||||
if (index == -1)
|
|
||||||
{
|
|
||||||
index = this->vector_.size();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(index >= 0 && index <= this->vector_.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
this->vector_.insert(this->vector_.begin() + index, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
|
||||||
this->itemInserted.invoke(args);
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isSorted() const override
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TVectorItem, typename Compare>
|
|
||||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual int insertItem(const TVectorItem &item, int = -1,
|
|
||||||
void *caller = nullptr) override
|
|
||||||
{
|
|
||||||
assertInGuiThread();
|
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::unique_lock lock(this->mutex_);
|
|
||||||
|
|
||||||
auto it = std::lower_bound(this->vector_.begin(),
|
|
||||||
this->vector_.end(), item, Compare{});
|
|
||||||
index = it - this->vector_.begin();
|
|
||||||
this->vector_.insert(it, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
|
||||||
this->itemInserted.invoke(args);
|
|
||||||
this->invokeDelayedItemsChanged();
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isSorted() const override
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(BaseSignalVector<TVectorItem> *vec)
|
void initialize(SignalVector<TVectorItem> *vec)
|
||||||
{
|
{
|
||||||
this->vector_ = vec;
|
this->vector_ = vec;
|
||||||
|
|
||||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
auto insert = [this](const SignalVectorItemEvent<TVectorItem> &args) {
|
||||||
if (args.caller == this)
|
if (args.caller == this)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -52,9 +52,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const TVectorItem &item : vec->getVector())
|
for (const TVectorItem &item : vec->raw())
|
||||||
{
|
{
|
||||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
SignalVectorItemEvent<TVectorItem> args{item, i++, 0};
|
||||||
|
|
||||||
insert(args);
|
insert(args);
|
||||||
}
|
}
|
||||||
@@ -89,6 +89,12 @@ public:
|
|||||||
this->afterInit();
|
this->afterInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignalVectorModel<TVectorItem> *initialized(SignalVector<TVectorItem> *vec)
|
||||||
|
{
|
||||||
|
this->initialize(vec);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
virtual ~SignalVectorModel()
|
virtual ~SignalVectorModel()
|
||||||
{
|
{
|
||||||
for (Row &row : this->rows_)
|
for (Row &row : this->rows_)
|
||||||
@@ -147,12 +153,12 @@ public:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||||
this->vector_->removeItem(vecRow, this);
|
this->vector_->removeAt(vecRow, this);
|
||||||
|
|
||||||
assert(this->rows_[row].original);
|
assert(this->rows_[row].original);
|
||||||
TVectorItem item = this->getItemFromRow(
|
TVectorItem item = this->getItemFromRow(
|
||||||
this->rows_[row].items, this->rows_[row].original.get());
|
this->rows_[row].items, this->rows_[row].original.get());
|
||||||
this->vector_->insertItem(item, vecRow, this);
|
this->vector_->insert(item, vecRow, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -219,7 +225,7 @@ public:
|
|||||||
void deleteRow(int row)
|
void deleteRow(int row)
|
||||||
{
|
{
|
||||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||||
this->vector_->removeItem(signalVectorRow);
|
this->vector_->removeAt(signalVectorRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool removeRows(int row, int count, const QModelIndex &parent) override
|
bool removeRows(int row, int count, const QModelIndex &parent) override
|
||||||
@@ -234,11 +240,71 @@ public:
|
|||||||
assert(row >= 0 && row < this->rows_.size());
|
assert(row >= 0 && row < this->rows_.size());
|
||||||
|
|
||||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||||
this->vector_->removeItem(signalVectorRow);
|
this->vector_->removeAt(signalVectorRow);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList mimeTypes() const override
|
||||||
|
{
|
||||||
|
return {"chatterino_row_id"};
|
||||||
|
}
|
||||||
|
|
||||||
|
QMimeData *mimeData(const QModelIndexList &list) const
|
||||||
|
{
|
||||||
|
if (list.length() == 1)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all indices are in the same row -> single row selected
|
||||||
|
for (auto &&x : list)
|
||||||
|
{
|
||||||
|
if (x.row() != list.first().row())
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = new QMimeData;
|
||||||
|
data->setData("chatterino_row_id", QByteArray::number(list[0].row()));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int /*row*/,
|
||||||
|
int /*column*/, const QModelIndex &parent) override
|
||||||
|
{
|
||||||
|
if (data->hasFormat("chatterino_row_id") &&
|
||||||
|
action & (Qt::DropAction::MoveAction | Qt::DropAction::CopyAction))
|
||||||
|
{
|
||||||
|
int from = data->data("chatterino_row_id").toInt();
|
||||||
|
int to = parent.row();
|
||||||
|
|
||||||
|
if (from < 0 || from > this->vector_->raw().size() || to < 0 ||
|
||||||
|
to > this->vector_->raw().size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from != to)
|
||||||
|
{
|
||||||
|
auto item = this->vector_->raw()[from];
|
||||||
|
this->vector_->removeAt(from);
|
||||||
|
this->vector_->insert(item, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We return false since we remove items ourselves.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::DropActions supportedDropActions() const override
|
||||||
|
{
|
||||||
|
return this->vector_->isSorted()
|
||||||
|
? Qt::DropActions()
|
||||||
|
: Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void afterInit()
|
virtual void afterInit()
|
||||||
{
|
{
|
||||||
@@ -326,7 +392,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<QMap<int, QVariant>> headerData_;
|
std::vector<QMap<int, QVariant>> headerData_;
|
||||||
BaseSignalVector<TVectorItem> *vector_;
|
SignalVector<TVectorItem> *vector_;
|
||||||
std::vector<Row> rows_;
|
std::vector<Row> rows_;
|
||||||
|
|
||||||
int columnCount_;
|
int columnCount_;
|
||||||
|
|||||||
@@ -7,20 +7,20 @@
|
|||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
AccountController::AccountController()
|
AccountController::AccountController()
|
||||||
|
: accounts_(SharedPtrElementLess<Account>{})
|
||||||
{
|
{
|
||||||
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
||||||
this->accounts_.insertItem(
|
this->accounts_.insert(std::dynamic_pointer_cast<Account>(args.item));
|
||||||
std::dynamic_pointer_cast<Account>(args.item));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||||
if (args.caller != this)
|
if (args.caller != this)
|
||||||
{
|
{
|
||||||
auto &accs = this->twitch.accounts.getVector();
|
auto &accs = this->twitch.accounts.raw();
|
||||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||||
assert(it != accs.end());
|
assert(it != accs.end());
|
||||||
|
|
||||||
this->accounts_.removeItem(it - accs.begin(), this);
|
this->accounts_.removeAt(it - accs.begin(), this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,10 +30,10 @@ AccountController::AccountController()
|
|||||||
case ProviderId::Twitch: {
|
case ProviderId::Twitch: {
|
||||||
if (args.caller != this)
|
if (args.caller != this)
|
||||||
{
|
{
|
||||||
auto accs = this->twitch.accounts.cloneVector();
|
auto &&accs = this->twitch.accounts;
|
||||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||||
assert(it != accs.end());
|
assert(it != accs.end());
|
||||||
this->twitch.accounts.removeItem(it - accs.begin(), this);
|
this->twitch.accounts.removeAt(it - accs.begin(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -50,7 +50,7 @@ AccountModel *AccountController::createModel(QObject *parent)
|
|||||||
{
|
{
|
||||||
AccountModel *model = new AccountModel(parent);
|
AccountModel *model = new AccountModel(parent);
|
||||||
|
|
||||||
model->init(&this->accounts_);
|
model->initialize(&this->accounts_);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ public:
|
|||||||
TwitchAccountManager twitch;
|
TwitchAccountManager twitch;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SortedSignalVector<std::shared_ptr<Account>, SharedPtrElementLess<Account>>
|
SignalVector<std::shared_ptr<Account>> accounts_;
|
||||||
accounts_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/twitch/TwitchApi.hpp"
|
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
@@ -212,7 +212,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
// Update the setting when the vector of commands has been updated (most
|
// Update the setting when the vector of commands has been updated (most
|
||||||
// likely from the settings dialog)
|
// likely from the settings dialog)
|
||||||
this->items_.delayedItemsChanged.connect([this] { //
|
this->items_.delayedItemsChanged.connect([this] { //
|
||||||
this->commandsSetting_->setValue(this->items_.getVector());
|
this->commandsSetting_->setValue(this->items_.raw());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load commands from commands.json
|
// Load commands from commands.json
|
||||||
@@ -222,7 +222,7 @@ void CommandController::initialize(Settings &, Paths &paths)
|
|||||||
// of commands)
|
// of commands)
|
||||||
for (const auto &command : this->commandsSetting_->getValue())
|
for (const auto &command : this->commandsSetting_->getValue())
|
||||||
{
|
{
|
||||||
this->items_.appendItem(command);
|
this->items_.append(command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ void CommandController::save()
|
|||||||
CommandModel *CommandController::createModel(QObject *parent)
|
CommandModel *CommandController::createModel(QObject *parent)
|
||||||
{
|
{
|
||||||
CommandModel *model = new CommandModel(parent);
|
CommandModel *model = new CommandModel(parent);
|
||||||
model->init(&this->items_);
|
model->initialize(&this->items_);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
@@ -245,8 +245,6 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
|||||||
QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
|
QString text = getApp()->emotes->emojis.replaceShortCodes(textNoEmoji);
|
||||||
QStringList words = text.split(' ', QString::SkipEmptyParts);
|
QStringList words = text.split(' ', QString::SkipEmptyParts);
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(this->mutex_);
|
|
||||||
|
|
||||||
if (words.length() == 0)
|
if (words.length() == 0)
|
||||||
{
|
{
|
||||||
return text;
|
return text;
|
||||||
@@ -366,18 +364,17 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchApi::findUserId(
|
getHelix()->getUserByName(
|
||||||
target, [user, channel, target](QString userId) {
|
target,
|
||||||
if (userId.isEmpty())
|
[user, channel, target](const auto &targetUser) {
|
||||||
{
|
user->followUser(targetUser.id, [channel, target]() {
|
||||||
channel->addMessage(makeSystemMessage(
|
|
||||||
"User " + target + " could not be followed!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user->followUser(userId, [channel, target]() {
|
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
"You successfully followed " + target));
|
"You successfully followed " + target));
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
[channel, target] {
|
||||||
|
channel->addMessage(makeSystemMessage(
|
||||||
|
"User " + target + " could not be followed!"));
|
||||||
});
|
});
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
@@ -402,18 +399,17 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
TwitchApi::findUserId(
|
getHelix()->getUserByName(
|
||||||
target, [user, channel, target](QString userId) {
|
target,
|
||||||
if (userId.isEmpty())
|
[user, channel, target](const auto &targetUser) {
|
||||||
{
|
user->unfollowUser(targetUser.id, [channel, target]() {
|
||||||
channel->addMessage(makeSystemMessage(
|
|
||||||
"User " + target + " could not be followed!"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user->unfollowUser(userId, [channel, target]() {
|
|
||||||
channel->addMessage(makeSystemMessage(
|
channel->addMessage(makeSystemMessage(
|
||||||
"You successfully unfollowed " + target));
|
"You successfully unfollowed " + target));
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
[channel, target] {
|
||||||
|
channel->addMessage(makeSystemMessage(
|
||||||
|
"User " + target + " could not be followed!"));
|
||||||
});
|
});
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class CommandModel;
|
|||||||
class CommandController final : public Singleton
|
class CommandController final : public Singleton
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
UnsortedSignalVector<Command> items_;
|
SignalVector<Command> items_;
|
||||||
|
|
||||||
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
QString execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||||
bool dryRun);
|
bool dryRun);
|
||||||
@@ -39,8 +39,6 @@ private:
|
|||||||
QMap<QString, Command> commandsMap_;
|
QMap<QString, Command> commandsMap_;
|
||||||
int maxSpaces_ = 0;
|
int maxSpaces_ = 0;
|
||||||
|
|
||||||
std::mutex mutex_;
|
|
||||||
|
|
||||||
std::shared_ptr<pajlada::Settings::SettingManager> sm_;
|
std::shared_ptr<pajlada::Settings::SettingManager> sm_;
|
||||||
// Because the setting manager is not initialized until the initialize
|
// Because the setting manager is not initialized until the initialize
|
||||||
// function is called (and not in the constructor), we have to
|
// function is called (and not in the constructor), we have to
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "CommandModel.hpp"
|
#include "CommandModel.hpp"
|
||||||
|
|
||||||
|
#include "util/StandardItemHelper.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
// commandmodel
|
// commandmodel
|
||||||
@@ -20,12 +22,8 @@ Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
|||||||
void CommandModel::getRowFromItem(const Command &item,
|
void CommandModel::getRowFromItem(const Command &item,
|
||||||
std::vector<QStandardItem *> &row)
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
row[0]->setData(item.name, Qt::DisplayRole);
|
setStringItem(row[0], item.name);
|
||||||
row[0]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
setStringItem(row[1], item.func);
|
||||||
Qt::ItemIsEditable);
|
|
||||||
row[1]->setData(item.func, Qt::DisplayRole);
|
|
||||||
row[1]->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable |
|
|
||||||
Qt::ItemIsEditable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ class HighlightController;
|
|||||||
|
|
||||||
class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
explicit HighlightBlacklistModel(QObject *parent);
|
explicit HighlightBlacklistModel(QObject *parent);
|
||||||
|
|
||||||
public:
|
|
||||||
enum Column {
|
enum Column {
|
||||||
Pattern = 0,
|
Pattern = 0,
|
||||||
UseRegex = 1,
|
UseRegex = 1,
|
||||||
@@ -28,8 +28,6 @@ protected:
|
|||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const HighlightBlacklistUser &item,
|
virtual void getRowFromItem(const HighlightBlacklistUser &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class HighlightController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
#include "HighlightController.hpp"
|
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "controllers/highlights/HighlightBlacklistModel.hpp"
|
|
||||||
#include "controllers/highlights/HighlightModel.hpp"
|
|
||||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
|
||||||
#include "widgets/dialogs/NotificationPopup.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
HighlightController::HighlightController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void HighlightController::initialize(Settings &settings, Paths &paths)
|
|
||||||
{
|
|
||||||
assert(!this->initialized_);
|
|
||||||
this->initialized_ = true;
|
|
||||||
|
|
||||||
for (const HighlightPhrase &phrase : this->highlightsSetting_.getValue())
|
|
||||||
{
|
|
||||||
this->phrases.appendItem(phrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->phrases.delayedItemsChanged.connect([this] { //
|
|
||||||
this->highlightsSetting_.setValue(this->phrases.getVector());
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const HighlightBlacklistUser &blacklistedUser :
|
|
||||||
this->blacklistSetting_.getValue())
|
|
||||||
{
|
|
||||||
this->blacklistedUsers.appendItem(blacklistedUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->blacklistedUsers.delayedItemsChanged.connect([this] {
|
|
||||||
this->blacklistSetting_.setValue(this->blacklistedUsers.getVector());
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const HighlightPhrase &user : this->userSetting_.getValue())
|
|
||||||
{
|
|
||||||
this->highlightedUsers.appendItem(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->highlightedUsers.delayedItemsChanged.connect([this] { //
|
|
||||||
this->userSetting_.setValue(this->highlightedUsers.getVector());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HighlightModel *HighlightController::createModel(QObject *parent)
|
|
||||||
{
|
|
||||||
HighlightModel *model = new HighlightModel(parent);
|
|
||||||
model->init(&this->phrases);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserHighlightModel *HighlightController::createUserModel(QObject *parent)
|
|
||||||
{
|
|
||||||
auto *model = new UserHighlightModel(parent);
|
|
||||||
model->init(&this->highlightedUsers);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HighlightController::isHighlightedUser(const QString &username)
|
|
||||||
{
|
|
||||||
const auto &userItems = this->highlightedUsers;
|
|
||||||
for (const auto &highlightedUser : userItems)
|
|
||||||
{
|
|
||||||
if (highlightedUser.isMatch(username))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HighlightBlacklistModel *HighlightController::createBlacklistModel(
|
|
||||||
QObject *parent)
|
|
||||||
{
|
|
||||||
auto *model = new HighlightBlacklistModel(parent);
|
|
||||||
model->init(&this->blacklistedUsers);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HighlightController::blacklistContains(const QString &username)
|
|
||||||
{
|
|
||||||
for (const auto &blacklistedUser : this->blacklistedUsers)
|
|
||||||
{
|
|
||||||
if (blacklistedUser.isMatch(username))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HighlightController::addHighlight(const MessagePtr &msg)
|
|
||||||
{
|
|
||||||
// static NotificationPopup popup;
|
|
||||||
|
|
||||||
// popup.updatePosition();
|
|
||||||
// popup.addMessage(msg);
|
|
||||||
// popup.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
|
||||||
#include "controllers/highlights/HighlightPhrase.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct Message;
|
|
||||||
using MessagePtr = std::shared_ptr<const Message>;
|
|
||||||
|
|
||||||
class Settings;
|
|
||||||
class Paths;
|
|
||||||
|
|
||||||
class UserHighlightModel;
|
|
||||||
class HighlightModel;
|
|
||||||
class HighlightBlacklistModel;
|
|
||||||
|
|
||||||
class HighlightController final : public Singleton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HighlightController();
|
|
||||||
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
|
||||||
|
|
||||||
UnsortedSignalVector<HighlightPhrase> phrases;
|
|
||||||
UnsortedSignalVector<HighlightBlacklistUser> blacklistedUsers;
|
|
||||||
UnsortedSignalVector<HighlightPhrase> highlightedUsers;
|
|
||||||
|
|
||||||
HighlightModel *createModel(QObject *parent);
|
|
||||||
HighlightBlacklistModel *createBlacklistModel(QObject *parent);
|
|
||||||
UserHighlightModel *createUserModel(QObject *parent);
|
|
||||||
|
|
||||||
bool isHighlightedUser(const QString &username);
|
|
||||||
bool blacklistContains(const QString &username);
|
|
||||||
|
|
||||||
void addHighlight(const MessagePtr &msg);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
ChatterinoSetting<std::vector<HighlightPhrase>> highlightsSetting_ = {
|
|
||||||
"/highlighting/highlights"};
|
|
||||||
ChatterinoSetting<std::vector<HighlightBlacklistUser>> blacklistSetting_ = {
|
|
||||||
"/highlighting/blacklist"};
|
|
||||||
ChatterinoSetting<std::vector<HighlightPhrase>> userSetting_ = {
|
|
||||||
"/highlighting/users"};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/StandardItemHelper.hpp"
|
#include "util/StandardItemHelper.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
@@ -63,10 +64,10 @@ void HighlightModel::afterInit()
|
|||||||
usernameRow[Column::CaseSensitive]->setFlags(0);
|
usernameRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
|
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
|
||||||
setFilePathItem(usernameRow[Column::SoundPath], selfSound);
|
setFilePathItem(usernameRow[Column::SoundPath], selfSound, false);
|
||||||
|
|
||||||
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
|
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
|
||||||
setColorItem(usernameRow[Column::Color], *selfColor);
|
setColorItem(usernameRow[Column::Color], *selfColor, false);
|
||||||
|
|
||||||
this->insertCustomRow(usernameRow, 0);
|
this->insertCustomRow(usernameRow, 0);
|
||||||
|
|
||||||
@@ -86,12 +87,13 @@ void HighlightModel::afterInit()
|
|||||||
|
|
||||||
QUrl whisperSound =
|
QUrl whisperSound =
|
||||||
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||||
setFilePathItem(whisperRow[Column::SoundPath], whisperSound);
|
setFilePathItem(whisperRow[Column::SoundPath], whisperSound, false);
|
||||||
|
|
||||||
auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
// auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
||||||
setColorItem(whisperRow[Column::Color], *whisperColor);
|
// setColorItem(whisperRow[Column::Color], *whisperColor, false);
|
||||||
|
whisperRow[Column::Color]->setFlags(Qt::ItemFlag::NoItemFlags);
|
||||||
|
|
||||||
this->insertCustomRow(whisperRow, 1);
|
this->insertCustomRow(whisperRow, WHISPER_ROW);
|
||||||
|
|
||||||
// Highlight settings for subscription messages
|
// Highlight settings for subscription messages
|
||||||
std::vector<QStandardItem *> subRow = this->createRow();
|
std::vector<QStandardItem *> subRow = this->createRow();
|
||||||
@@ -107,12 +109,37 @@ void HighlightModel::afterInit()
|
|||||||
subRow[Column::CaseSensitive]->setFlags(0);
|
subRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
||||||
setFilePathItem(subRow[Column::SoundPath], subSound);
|
setFilePathItem(subRow[Column::SoundPath], subSound, false);
|
||||||
|
|
||||||
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
|
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
|
||||||
setColorItem(subRow[Column::Color], *subColor);
|
setColorItem(subRow[Column::Color], *subColor, false);
|
||||||
|
|
||||||
this->insertCustomRow(subRow, 2);
|
this->insertCustomRow(subRow, 2);
|
||||||
|
|
||||||
|
// Highlight settings for redeemed highlight messages
|
||||||
|
std::vector<QStandardItem *> redeemedRow = this->createRow();
|
||||||
|
setBoolItem(redeemedRow[Column::Pattern],
|
||||||
|
getSettings()->enableRedeemedHighlight.getValue(), true, false);
|
||||||
|
redeemedRow[Column::Pattern]->setData("Highlights redeemed with Bits",
|
||||||
|
Qt::DisplayRole);
|
||||||
|
setBoolItem(redeemedRow[Column::FlashTaskbar],
|
||||||
|
getSettings()->enableRedeemedHighlightTaskbar.getValue(), true,
|
||||||
|
false);
|
||||||
|
setBoolItem(redeemedRow[Column::PlaySound],
|
||||||
|
getSettings()->enableRedeemedHighlightSound.getValue(), true,
|
||||||
|
false);
|
||||||
|
redeemedRow[Column::UseRegex]->setFlags(0);
|
||||||
|
redeemedRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
|
QUrl RedeemedSound =
|
||||||
|
QUrl(getSettings()->redeemedHighlightSoundUrl.getValue());
|
||||||
|
setFilePathItem(redeemedRow[Column::SoundPath], RedeemedSound, false);
|
||||||
|
|
||||||
|
auto RedeemedColor =
|
||||||
|
ColorProvider::instance().color(ColorType::RedeemedHighlight);
|
||||||
|
setColorItem(redeemedRow[Column::Color], *RedeemedColor, false);
|
||||||
|
|
||||||
|
this->insertCustomRow(redeemedRow, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
@@ -128,7 +155,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
{
|
{
|
||||||
getSettings()->enableSelfHighlight.setValue(value.toBool());
|
getSettings()->enableSelfHighlight.setValue(value.toBool());
|
||||||
}
|
}
|
||||||
else if (rowIndex == 1)
|
else if (rowIndex == WHISPER_ROW)
|
||||||
{
|
{
|
||||||
getSettings()->enableWhisperHighlight.setValue(
|
getSettings()->enableWhisperHighlight.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
@@ -137,6 +164,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
{
|
{
|
||||||
getSettings()->enableSubHighlight.setValue(value.toBool());
|
getSettings()->enableSubHighlight.setValue(value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 3)
|
||||||
|
{
|
||||||
|
getSettings()->enableRedeemedHighlight.setValue(
|
||||||
|
value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -148,7 +180,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->enableSelfHighlightTaskbar.setValue(
|
getSettings()->enableSelfHighlightTaskbar.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
else if (rowIndex == 1)
|
else if (rowIndex == WHISPER_ROW)
|
||||||
{
|
{
|
||||||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
@@ -158,6 +190,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->enableSubHighlightTaskbar.setValue(
|
getSettings()->enableSubHighlightTaskbar.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 3)
|
||||||
|
{
|
||||||
|
getSettings()->enableRedeemedHighlightTaskbar.setValue(
|
||||||
|
value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -169,7 +206,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->enableSelfHighlightSound.setValue(
|
getSettings()->enableSelfHighlightSound.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
else if (rowIndex == 1)
|
else if (rowIndex == WHISPER_ROW)
|
||||||
{
|
{
|
||||||
getSettings()->enableWhisperHighlightSound.setValue(
|
getSettings()->enableWhisperHighlightSound.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
@@ -179,6 +216,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->enableSubHighlightSound.setValue(
|
getSettings()->enableSubHighlightSound.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 3)
|
||||||
|
{
|
||||||
|
getSettings()->enableRedeemedHighlightSound.setValue(
|
||||||
|
value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -199,7 +241,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->selfHighlightSoundUrl.setValue(
|
getSettings()->selfHighlightSoundUrl.setValue(
|
||||||
value.toString());
|
value.toString());
|
||||||
}
|
}
|
||||||
else if (rowIndex == 1)
|
else if (rowIndex == WHISPER_ROW)
|
||||||
{
|
{
|
||||||
getSettings()->whisperHighlightSoundUrl.setValue(
|
getSettings()->whisperHighlightSoundUrl.setValue(
|
||||||
value.toString());
|
value.toString());
|
||||||
@@ -209,6 +251,11 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
getSettings()->subHighlightSoundUrl.setValue(
|
getSettings()->subHighlightSoundUrl.setValue(
|
||||||
value.toString());
|
value.toString());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 3)
|
||||||
|
{
|
||||||
|
getSettings()->redeemedHighlightSoundUrl.setValue(
|
||||||
|
value.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -221,18 +268,27 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||||||
{
|
{
|
||||||
getSettings()->selfHighlightColor.setValue(colorName);
|
getSettings()->selfHighlightColor.setValue(colorName);
|
||||||
}
|
}
|
||||||
else if (rowIndex == 1)
|
// else if (rowIndex == WHISPER_ROW)
|
||||||
{
|
// {
|
||||||
getSettings()->whisperHighlightColor.setValue(colorName);
|
// getSettings()->whisperHighlightColor.setValue(colorName);
|
||||||
}
|
// }
|
||||||
else if (rowIndex == 2)
|
else if (rowIndex == 2)
|
||||||
{
|
{
|
||||||
getSettings()->subHighlightColor.setValue(colorName);
|
getSettings()->subHighlightColor.setValue(colorName);
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 3)
|
||||||
|
{
|
||||||
|
getSettings()->redeemedHighlightColor.setValue(colorName);
|
||||||
|
const_cast<ColorProvider &>(ColorProvider::instance())
|
||||||
|
.updateColor(ColorType::RedeemedHighlight,
|
||||||
|
QColor(colorName));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getApp()->windows->forceLayoutChannelViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -7,13 +7,11 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class HighlightController;
|
|
||||||
|
|
||||||
class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
explicit HighlightModel(QObject *parent);
|
explicit HighlightModel(QObject *parent);
|
||||||
|
|
||||||
public:
|
|
||||||
// Used here, in HighlightingPage and in UserHighlightModel
|
// Used here, in HighlightingPage and in UserHighlightModel
|
||||||
enum Column {
|
enum Column {
|
||||||
Pattern = 0,
|
Pattern = 0,
|
||||||
@@ -25,6 +23,8 @@ public:
|
|||||||
Color = 6
|
Color = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr static int WHISPER_ROW = 1;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual HighlightPhrase getItemFromRow(
|
virtual HighlightPhrase getItemFromRow(
|
||||||
@@ -40,8 +40,6 @@ protected:
|
|||||||
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
int column, const QVariant &value, int role,
|
int column, const QVariant &value, int role,
|
||||||
int rowIndex) override;
|
int rowIndex) override;
|
||||||
|
|
||||||
friend class HighlightController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
QColor HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR = QColor(127, 63, 73, 127);
|
||||||
|
QColor HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR =
|
||||||
|
QColor(28, 126, 141, 90);
|
||||||
|
QColor HighlightPhrase::FALLBACK_SUB_COLOR = QColor(196, 102, 255, 100);
|
||||||
|
|
||||||
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
|
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
|
||||||
{
|
{
|
||||||
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
|
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ public:
|
|||||||
const QUrl &getSoundUrl() const;
|
const QUrl &getSoundUrl() const;
|
||||||
const std::shared_ptr<QColor> getColor() const;
|
const std::shared_ptr<QColor> getColor() const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XXX: Use the constexpr constructor here once we are building with
|
||||||
|
* Qt>=5.13.
|
||||||
|
*/
|
||||||
|
static QColor FALLBACK_HIGHLIGHT_COLOR;
|
||||||
|
static QColor FALLBACK_REDEEMED_HIGHLIGHT_COLOR;
|
||||||
|
static QColor FALLBACK_SUB_COLOR;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString pattern_;
|
QString pattern_;
|
||||||
bool hasAlert_;
|
bool hasAlert_;
|
||||||
@@ -132,6 +140,8 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||||||
chatterino::rj::getSafe(value, "color", encodedColor);
|
chatterino::rj::getSafe(value, "color", encodedColor);
|
||||||
|
|
||||||
auto _color = QColor(encodedColor);
|
auto _color = QColor(encodedColor);
|
||||||
|
if (!_color.isValid())
|
||||||
|
_color = chatterino::HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR;
|
||||||
|
|
||||||
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
|
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
|
||||||
_isRegex, _isCaseSensitive,
|
_isRegex, _isCaseSensitive,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class HighlightController;
|
|||||||
|
|
||||||
class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
class UserHighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
explicit UserHighlightModel(QObject *parent);
|
explicit UserHighlightModel(QObject *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -21,8 +22,6 @@ protected:
|
|||||||
|
|
||||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class HighlightController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
#include "controllers/ignores/IgnoreController.hpp"
|
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "controllers/ignores/IgnoreModel.hpp"
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
void IgnoreController::initialize(Settings &, Paths &)
|
|
||||||
{
|
|
||||||
assert(!this->initialized_);
|
|
||||||
this->initialized_ = true;
|
|
||||||
|
|
||||||
for (const IgnorePhrase &phrase : this->ignoresSetting_.getValue())
|
|
||||||
{
|
|
||||||
this->phrases.appendItem(phrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->phrases.delayedItemsChanged.connect([this] { //
|
|
||||||
this->ignoresSetting_.setValue(this->phrases.getVector());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
IgnoreModel *IgnoreController::createModel(QObject *parent)
|
|
||||||
{
|
|
||||||
IgnoreModel *model = new IgnoreModel(parent);
|
|
||||||
model->init(&this->phrases);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,33 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
#include "controllers/ignores/IgnorePhrase.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class Settings;
|
|
||||||
class Paths;
|
|
||||||
|
|
||||||
class IgnoreModel;
|
|
||||||
|
|
||||||
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
|
enum class ShowIgnoredUsersMessages { Never, IfModerator, IfBroadcaster };
|
||||||
|
|
||||||
class IgnoreController final : public Singleton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
|
||||||
|
|
||||||
UnsortedSignalVector<IgnorePhrase> phrases;
|
|
||||||
|
|
||||||
IgnoreModel *createModel(QObject *parent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
ChatterinoSetting<std::vector<IgnorePhrase>> ignoresSetting_ = {
|
|
||||||
"/ignore/phrases"};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -7,10 +7,9 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class IgnoreController;
|
|
||||||
|
|
||||||
class IgnoreModel : public SignalVectorModel<IgnorePhrase>
|
class IgnoreModel : public SignalVectorModel<IgnorePhrase>
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
explicit IgnoreModel(QObject *parent);
|
explicit IgnoreModel(QObject *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -21,8 +20,6 @@ protected:
|
|||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const IgnorePhrase &item,
|
virtual void getRowFromItem(const IgnorePhrase &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class IgnoreController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ ModerationAction::ModerationAction(const QString &action)
|
|||||||
}
|
}
|
||||||
else if (action.startsWith("/ban "))
|
else if (action.startsWith("/ban "))
|
||||||
{
|
{
|
||||||
this->image_ = Image::fromPixmap(getResources().buttons.ban);
|
this->imageToLoad_ = 1;
|
||||||
}
|
}
|
||||||
else if (action.startsWith("/delete "))
|
else if (action.startsWith("/delete "))
|
||||||
{
|
{
|
||||||
this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
|
this->imageToLoad_ = 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -100,6 +100,16 @@ bool ModerationAction::isImage() const
|
|||||||
|
|
||||||
const boost::optional<ImagePtr> &ModerationAction::getImage() const
|
const boost::optional<ImagePtr> &ModerationAction::getImage() const
|
||||||
{
|
{
|
||||||
|
assertInGuiThread();
|
||||||
|
|
||||||
|
if (this->imageToLoad_ != 0)
|
||||||
|
{
|
||||||
|
if (this->imageToLoad_ == 1)
|
||||||
|
this->image_ = Image::fromPixmap(getResources().buttons.ban);
|
||||||
|
else if (this->imageToLoad_ == 2)
|
||||||
|
this->image_ = Image::fromPixmap(getResources().buttons.trashCan);
|
||||||
|
}
|
||||||
|
|
||||||
return this->image_;
|
return this->image_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ public:
|
|||||||
const QString &getAction() const;
|
const QString &getAction() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::optional<ImagePtr> image_;
|
mutable boost::optional<ImagePtr> image_;
|
||||||
QString line1_;
|
QString line1_;
|
||||||
QString line2_;
|
QString line2_;
|
||||||
QString action_;
|
QString action_;
|
||||||
|
int imageToLoad_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class ModerationActions;
|
|
||||||
|
|
||||||
class ModerationActionModel : public SignalVectorModel<ModerationAction>
|
class ModerationActionModel : public SignalVectorModel<ModerationAction>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -25,8 +23,6 @@ protected:
|
|||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class HighlightController;
|
friend class HighlightController;
|
||||||
|
|
||||||
friend class ModerationActions;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
#include "ModerationActions.hpp"
|
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "controllers/moderationactions/ModerationActionModel.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
|
|
||||||
#include <QRegularExpression>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
ModerationActions::ModerationActions()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModerationActions::initialize(Settings &settings, Paths &paths)
|
|
||||||
{
|
|
||||||
assert(!this->initialized_);
|
|
||||||
this->initialized_ = true;
|
|
||||||
|
|
||||||
this->setting_ =
|
|
||||||
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
|
|
||||||
"/moderation/actions");
|
|
||||||
|
|
||||||
for (auto &val : this->setting_->getValue())
|
|
||||||
{
|
|
||||||
this->items.insertItem(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->items.delayedItemsChanged.connect([this] { //
|
|
||||||
this->setting_->setValue(this->items.getVector());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ModerationActionModel *ModerationActions::createModel(QObject *parent)
|
|
||||||
{
|
|
||||||
ModerationActionModel *model = new ModerationActionModel(parent);
|
|
||||||
model->init(&this->items);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
|
|
||||||
#include "common/ChatterinoSetting.hpp"
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
#include "controllers/moderationactions/ModerationAction.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class Settings;
|
|
||||||
class Paths;
|
|
||||||
|
|
||||||
class ModerationActionModel;
|
|
||||||
|
|
||||||
class ModerationActions final : public Singleton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ModerationActions();
|
|
||||||
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
|
||||||
|
|
||||||
UnsortedSignalVector<ModerationAction> items;
|
|
||||||
|
|
||||||
ModerationActionModel *createModel(QObject *parent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "controllers/notifications/NotificationModel.hpp"
|
#include "controllers/notifications/NotificationModel.hpp"
|
||||||
#include "providers/twitch/TwitchApi.hpp"
|
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
@@ -26,12 +26,11 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
|
|||||||
this->initialized_ = true;
|
this->initialized_ = true;
|
||||||
for (const QString &channelName : this->twitchSetting_.getValue())
|
for (const QString &channelName : this->twitchSetting_.getValue())
|
||||||
{
|
{
|
||||||
this->channelMap[Platform::Twitch].appendItem(channelName);
|
this->channelMap[Platform::Twitch].append(channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { //
|
this->channelMap[Platform::Twitch].delayedItemsChanged.connect([this] { //
|
||||||
this->twitchSetting_.setValue(
|
this->twitchSetting_.setValue(this->channelMap[Platform::Twitch].raw());
|
||||||
this->channelMap[Platform::Twitch].getVector());
|
|
||||||
});
|
});
|
||||||
/*
|
/*
|
||||||
for (const QString &channelName : this->mixerSetting_.getValue()) {
|
for (const QString &channelName : this->mixerSetting_.getValue()) {
|
||||||
@@ -81,18 +80,18 @@ bool NotificationController::isChannelNotified(const QString &channelName,
|
|||||||
void NotificationController::addChannelNotification(const QString &channelName,
|
void NotificationController::addChannelNotification(const QString &channelName,
|
||||||
Platform p)
|
Platform p)
|
||||||
{
|
{
|
||||||
channelMap[p].appendItem(channelName);
|
channelMap[p].append(channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationController::removeChannelNotification(
|
void NotificationController::removeChannelNotification(
|
||||||
const QString &channelName, Platform p)
|
const QString &channelName, Platform p)
|
||||||
{
|
{
|
||||||
for (std::vector<int>::size_type i = 0;
|
for (std::vector<int>::size_type i = 0; i != channelMap[p].raw().size();
|
||||||
i != channelMap[p].getVector().size(); i++)
|
i++)
|
||||||
{
|
{
|
||||||
if (channelMap[p].getVector()[i].toLower() == channelName.toLower())
|
if (channelMap[p].raw()[i].toLower() == channelName.toLower())
|
||||||
{
|
{
|
||||||
channelMap[p].removeItem(i);
|
channelMap[p].removeAt(i);
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,21 +120,21 @@ NotificationModel *NotificationController::createModel(QObject *parent,
|
|||||||
Platform p)
|
Platform p)
|
||||||
{
|
{
|
||||||
NotificationModel *model = new NotificationModel(parent);
|
NotificationModel *model = new NotificationModel(parent);
|
||||||
model->init(&this->channelMap[p]);
|
model->initialize(&this->channelMap[p]);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationController::fetchFakeChannels()
|
void NotificationController::fetchFakeChannels()
|
||||||
{
|
{
|
||||||
for (std::vector<int>::size_type i = 0;
|
for (std::vector<int>::size_type i = 0;
|
||||||
i != channelMap[Platform::Twitch].getVector().size(); i++)
|
i != channelMap[Platform::Twitch].raw().size(); i++)
|
||||||
{
|
{
|
||||||
auto chan = getApp()->twitch.server->getChannelOrEmpty(
|
auto chan = getApp()->twitch.server->getChannelOrEmpty(
|
||||||
channelMap[Platform::Twitch].getVector()[i]);
|
channelMap[Platform::Twitch].raw()[i]);
|
||||||
if (chan->isEmpty())
|
if (chan->isEmpty())
|
||||||
{
|
{
|
||||||
getFakeTwitchChannelLiveStatus(
|
getFakeTwitchChannelLiveStatus(
|
||||||
channelMap[Platform::Twitch].getVector()[i]);
|
channelMap[Platform::Twitch].raw()[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,68 +142,52 @@ void NotificationController::fetchFakeChannels()
|
|||||||
void NotificationController::getFakeTwitchChannelLiveStatus(
|
void NotificationController::getFakeTwitchChannelLiveStatus(
|
||||||
const QString &channelName)
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
|
getHelix()->getStreamByName(
|
||||||
if (roomID.isEmpty())
|
channelName,
|
||||||
{
|
[channelName, this](bool live, const auto &stream) {
|
||||||
|
qDebug() << "[TwitchChannel" << channelName
|
||||||
|
<< "] Refreshing live status";
|
||||||
|
|
||||||
|
if (!live)
|
||||||
|
{
|
||||||
|
// Stream is offline
|
||||||
|
this->removeFakeChannel(channelName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream is online
|
||||||
|
auto i = std::find(fakeTwitchChannels.begin(),
|
||||||
|
fakeTwitchChannels.end(), channelName);
|
||||||
|
|
||||||
|
if (i != fakeTwitchChannels.end())
|
||||||
|
{
|
||||||
|
// We have already pushed the live state of this stream
|
||||||
|
// Could not find stream in fake twitch channels!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Toasts::isEnabled())
|
||||||
|
{
|
||||||
|
getApp()->toasts->sendChannelNotification(channelName,
|
||||||
|
Platform::Twitch);
|
||||||
|
}
|
||||||
|
if (getSettings()->notificationPlaySound)
|
||||||
|
{
|
||||||
|
getApp()->notifications->playSound();
|
||||||
|
}
|
||||||
|
if (getSettings()->notificationFlashTaskbar)
|
||||||
|
{
|
||||||
|
getApp()->windows->sendAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that we have pushed notifications for this stream
|
||||||
|
fakeTwitchChannels.push_back(channelName);
|
||||||
|
},
|
||||||
|
[channelName, this] {
|
||||||
qDebug() << "[TwitchChannel" << channelName
|
qDebug() << "[TwitchChannel" << channelName
|
||||||
<< "] Refreshing live status (Missing ID)";
|
<< "] Refreshing live status (Missing ID)";
|
||||||
removeFakeChannel(channelName);
|
this->removeFakeChannel(channelName);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
qDebug() << "[TwitchChannel" << channelName
|
|
||||||
<< "] Refreshing live status";
|
|
||||||
|
|
||||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
|
||||||
NetworkRequest::twitchRequest(url)
|
|
||||||
.onSuccess([this, channelName](auto result) -> Outcome {
|
|
||||||
rapidjson::Document document = result.parseRapidJson();
|
|
||||||
if (!document.IsObject())
|
|
||||||
{
|
|
||||||
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not "
|
|
||||||
"an object";
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!document.HasMember("stream"))
|
|
||||||
{
|
|
||||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing "
|
|
||||||
"stream in root";
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &stream = document["stream"];
|
|
||||||
|
|
||||||
if (!stream.IsObject())
|
|
||||||
{
|
|
||||||
// Stream is offline (stream is most likely null)
|
|
||||||
// removeFakeChannel(channelName);
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
// Stream is live
|
|
||||||
auto i = std::find(fakeTwitchChannels.begin(),
|
|
||||||
fakeTwitchChannels.end(), channelName);
|
|
||||||
|
|
||||||
if (!(i != fakeTwitchChannels.end()))
|
|
||||||
{
|
|
||||||
fakeTwitchChannels.push_back(channelName);
|
|
||||||
if (Toasts::isEnabled())
|
|
||||||
{
|
|
||||||
getApp()->toasts->sendChannelNotification(
|
|
||||||
channelName, Platform::Twitch);
|
|
||||||
}
|
|
||||||
if (getSettings()->notificationPlaySound)
|
|
||||||
{
|
|
||||||
getApp()->notifications->playSound();
|
|
||||||
}
|
|
||||||
if (getSettings()->notificationFlashTaskbar)
|
|
||||||
{
|
|
||||||
getApp()->windows->sendAlert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationController::removeFakeChannel(const QString channelName)
|
void NotificationController::removeFakeChannel(const QString channelName)
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ public:
|
|||||||
|
|
||||||
void playSound();
|
void playSound();
|
||||||
|
|
||||||
UnsortedSignalVector<QString> getVector(Platform p);
|
SignalVector<QString> getVector(Platform p);
|
||||||
|
|
||||||
std::map<Platform, UnsortedSignalVector<QString>> channelMap;
|
std::map<Platform, SignalVector<QString>> channelMap;
|
||||||
|
|
||||||
NotificationModel *createModel(QObject *parent, Platform p);
|
NotificationModel *createModel(QObject *parent, Platform p);
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ private:
|
|||||||
void removeFakeChannel(const QString channelName);
|
void removeFakeChannel(const QString channelName);
|
||||||
void getFakeTwitchChannelLiveStatus(const QString &channelName);
|
void getFakeTwitchChannelLiveStatus(const QString &channelName);
|
||||||
|
|
||||||
|
// fakeTwitchChannels is a list of streams who are live that we have already sent out a notification for
|
||||||
std::vector<QString> fakeTwitchChannels;
|
std::vector<QString> fakeTwitchChannels;
|
||||||
QTimer *liveStatusTimer_;
|
QTimer *liveStatusTimer_;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include "MutedChannelModel.hpp"
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "util/StandardItemHelper.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
MutedChannelModel::MutedChannelModel(QObject *parent)
|
||||||
|
: SignalVectorModel<QString>(1, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn a vector item into a model row
|
||||||
|
QString MutedChannelModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||||
|
const QString &original)
|
||||||
|
{
|
||||||
|
return QString(row[0]->data(Qt::DisplayRole).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn a model
|
||||||
|
void MutedChannelModel::getRowFromItem(const QString &item,
|
||||||
|
std::vector<QStandardItem *> &row)
|
||||||
|
{
|
||||||
|
setStringItem(row[0], item);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class PingController;
|
class MutedChannelController;
|
||||||
|
|
||||||
class PingModel : public SignalVectorModel<QString>
|
class MutedChannelModel : public SignalVectorModel<QString>
|
||||||
{
|
{
|
||||||
explicit PingModel(QObject *parent);
|
explicit MutedChannelModel(QObject *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
@@ -21,8 +21,6 @@ protected:
|
|||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
virtual void getRowFromItem(const QString &item,
|
virtual void getRowFromItem(const QString &item,
|
||||||
std::vector<QStandardItem *> &row) override;
|
std::vector<QStandardItem *> &row) override;
|
||||||
|
|
||||||
friend class PingController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#include "controllers/pings/PingController.hpp"
|
|
||||||
#include "controllers/pings/PingModel.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
void PingController::initialize(Settings &settings, Paths &paths)
|
|
||||||
{
|
|
||||||
this->initialized_ = true;
|
|
||||||
for (const QString &channelName : this->pingSetting_.getValue())
|
|
||||||
{
|
|
||||||
this->channelVector.appendItem(channelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->channelVector.delayedItemsChanged.connect([this] { //
|
|
||||||
this->pingSetting_.setValue(this->channelVector.getVector());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
PingModel *PingController::createModel(QObject *parent)
|
|
||||||
{
|
|
||||||
PingModel *model = new PingModel(parent);
|
|
||||||
model->init(&this->channelVector);
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PingController::isMuted(const QString &channelName)
|
|
||||||
{
|
|
||||||
for (const auto &channel : this->channelVector)
|
|
||||||
{
|
|
||||||
if (channelName.toLower() == channel.toLower())
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingController::muteChannel(const QString &channelName)
|
|
||||||
{
|
|
||||||
channelVector.appendItem(channelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PingController::unmuteChannel(const QString &channelName)
|
|
||||||
{
|
|
||||||
for (std::vector<int>::size_type i = 0;
|
|
||||||
i != channelVector.getVector().size(); i++)
|
|
||||||
{
|
|
||||||
if (channelVector.getVector()[i].toLower() == channelName.toLower())
|
|
||||||
{
|
|
||||||
channelVector.removeItem(i);
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PingController::toggleMuteChannel(const QString &channelName)
|
|
||||||
{
|
|
||||||
if (this->isMuted(channelName))
|
|
||||||
{
|
|
||||||
unmuteChannel(channelName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
muteChannel(channelName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class Settings;
|
|
||||||
class Paths;
|
|
||||||
|
|
||||||
class PingModel;
|
|
||||||
|
|
||||||
class PingController final : public Singleton, private QObject
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual void initialize(Settings &settings, Paths &paths) override;
|
|
||||||
|
|
||||||
bool isMuted(const QString &channelName);
|
|
||||||
void muteChannel(const QString &channelName);
|
|
||||||
void unmuteChannel(const QString &channelName);
|
|
||||||
bool toggleMuteChannel(const QString &channelName);
|
|
||||||
|
|
||||||
PingModel *createModel(QObject *parent);
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
UnsortedSignalVector<QString> channelVector;
|
|
||||||
|
|
||||||
ChatterinoSetting<std::vector<QString>> pingSetting_ = {"/pings/muted"};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#include "PingModel.hpp"
|
|
||||||
|
|
||||||
#include "Application.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
|
||||||
#include "util/StandardItemHelper.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
PingModel::PingModel(QObject *parent)
|
|
||||||
: SignalVectorModel<QString>(1, parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn a vector item into a model row
|
|
||||||
QString PingModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
|
||||||
const QString &original)
|
|
||||||
{
|
|
||||||
return QString(row[0]->data(Qt::DisplayRole).toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn a model
|
|
||||||
void PingModel::getRowFromItem(const QString &item,
|
|
||||||
std::vector<QStandardItem *> &row)
|
|
||||||
{
|
|
||||||
setStringItem(row[0], item);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#include "TaggedUsersController.hpp"
|
|
||||||
|
|
||||||
#include "controllers/taggedusers/TaggedUsersModel.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
TaggedUsersController::TaggedUsersController()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
|
|
||||||
{
|
|
||||||
TaggedUsersModel *model = new TaggedUsersModel(parent);
|
|
||||||
model->init(&this->users);
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/Singleton.hpp"
|
|
||||||
|
|
||||||
#include "common/SignalVector.hpp"
|
|
||||||
#include "controllers/taggedusers/TaggedUser.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class TaggedUsersModel;
|
|
||||||
|
|
||||||
class TaggedUsersController final : public Singleton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TaggedUsersController();
|
|
||||||
|
|
||||||
SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
|
|
||||||
|
|
||||||
TaggedUsersModel *createModel(QObject *parent = nullptr);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include "common/Args.hpp"
|
#include "common/Args.hpp"
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
#include "providers/twitch/api/Kraken.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
@@ -36,6 +38,9 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Helix::initialize();
|
||||||
|
Kraken::initialize();
|
||||||
|
|
||||||
Paths *paths{};
|
Paths *paths{};
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|||||||
+13
-10
@@ -41,6 +41,14 @@ namespace detail {
|
|||||||
getApp()->emotes->gifTimer.signal.connect(
|
getApp()->emotes->gifTimer.signal.connect(
|
||||||
[this] { this->advance(); });
|
[this] { this->advance(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto totalLength = std::accumulate(
|
||||||
|
this->items_.begin(), this->items_.end(), 0UL,
|
||||||
|
[](auto init, auto &&frame) { return init + frame.duration; });
|
||||||
|
|
||||||
|
this->durationOffset_ = std::min<int>(
|
||||||
|
int(getApp()->emotes->gifTimer.position() % totalLength), 60000);
|
||||||
|
this->processOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Frames::~Frames()
|
Frames::~Frames()
|
||||||
@@ -58,17 +66,16 @@ namespace detail {
|
|||||||
|
|
||||||
void Frames::advance()
|
void Frames::advance()
|
||||||
{
|
{
|
||||||
this->durationOffset_ += GIF_FRAME_LENGTH;
|
this->durationOffset_ += gifFrameLength;
|
||||||
|
this->processOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Frames::processOffset()
|
||||||
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
this->index_ %= this->items_.size();
|
this->index_ %= this->items_.size();
|
||||||
|
|
||||||
// TODO: Figure out what this was supposed to achieve
|
|
||||||
// if (this->index_ >= this->items_.size()) {
|
|
||||||
// this->index_ = this->index_;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this->durationOffset_ > this->items_[this->index_].duration)
|
if (this->durationOffset_ > this->items_[this->index_].duration)
|
||||||
{
|
{
|
||||||
this->durationOffset_ -= this->items_[this->index_].duration;
|
this->durationOffset_ -= this->items_[this->index_].duration;
|
||||||
@@ -219,10 +226,6 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
|
|||||||
{
|
{
|
||||||
cache[url] = shared = ImagePtr(new Image(url, scale));
|
cache[url] = shared = ImagePtr(new Image(url, scale));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// qDebug() << "same image created multiple times:" << url.string;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shared;
|
return shared;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ namespace detail {
|
|||||||
boost::optional<QPixmap> first() const;
|
boost::optional<QPixmap> first() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void processOffset();
|
||||||
QVector<Frame<QPixmap>> items_;
|
QVector<Frame<QPixmap>> items_;
|
||||||
int index_{0};
|
int index_{0};
|
||||||
int durationOffset_{0};
|
int durationOffset_{0};
|
||||||
|
|||||||
@@ -125,8 +125,6 @@ public:
|
|||||||
newChunks->at(0) = newFirstChunk;
|
newChunks->at(0) = newFirstChunk;
|
||||||
|
|
||||||
this->chunks_ = newChunks;
|
this->chunks_ = newChunks;
|
||||||
// qDebug() << acceptedItems.size();
|
|
||||||
// qDebug() << this->chunks->at(0)->size();
|
|
||||||
|
|
||||||
if (this->chunks_->size() == 1)
|
if (this->chunks_->size() == 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ SBHighlight Message::getScrollBarHighlight() const
|
|||||||
return SBHighlight(
|
return SBHighlight(
|
||||||
ColorProvider::instance().color(ColorType::Subscription));
|
ColorProvider::instance().color(ColorType::Subscription));
|
||||||
}
|
}
|
||||||
|
else if (this->flags.has(MessageFlag::RedeemedHighlight))
|
||||||
|
{
|
||||||
|
return SBHighlight(
|
||||||
|
ColorProvider::instance().color(ColorType::RedeemedHighlight),
|
||||||
|
SBHighlight::Default, true);
|
||||||
|
}
|
||||||
return SBHighlight();
|
return SBHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ enum class MessageFlag : uint32_t {
|
|||||||
HighlightedWhisper = (1 << 17),
|
HighlightedWhisper = (1 << 17),
|
||||||
Debug = (1 << 18),
|
Debug = (1 << 18),
|
||||||
Similar = (1 << 19),
|
Similar = (1 << 19),
|
||||||
|
RedeemedHighlight = (1 << 20),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/LinkParser.hpp"
|
#include "common/LinkParser.hpp"
|
||||||
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
@@ -170,9 +171,12 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
|
|||||||
this->message().searchText = text;
|
this->message().searchText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: This does not belong in the MessageBuilder, this should be part of the TwitchMessageBuilder
|
||||||
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
|
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
|
||||||
: MessageBuilder()
|
: MessageBuilder()
|
||||||
{
|
{
|
||||||
|
auto current = getApp()->accounts->twitch.getCurrent();
|
||||||
|
|
||||||
this->emplace<TimestampElement>();
|
this->emplace<TimestampElement>();
|
||||||
this->message().flags.set(MessageFlag::System);
|
this->message().flags.set(MessageFlag::System);
|
||||||
this->message().flags.set(MessageFlag::Timeout);
|
this->message().flags.set(MessageFlag::Timeout);
|
||||||
@@ -181,43 +185,74 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
|
|||||||
|
|
||||||
QString text;
|
QString text;
|
||||||
|
|
||||||
if (action.isBan())
|
if (action.target.id == current->getUserId())
|
||||||
{
|
{
|
||||||
if (action.reason.isEmpty())
|
text.append("You were ");
|
||||||
|
if (action.isBan())
|
||||||
{
|
{
|
||||||
text = QString("%1 banned %2.") //
|
text.append("banned");
|
||||||
.arg(action.source.name)
|
|
||||||
.arg(action.target.name);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
text = QString("%1 banned %2: \"%3\".") //
|
text.append(
|
||||||
.arg(action.source.name)
|
QString("timed out for %1").arg(formatTime(action.duration)));
|
||||||
.arg(action.target.name)
|
}
|
||||||
.arg(action.reason);
|
|
||||||
|
if (!action.source.name.isEmpty())
|
||||||
|
{
|
||||||
|
text.append(" by ");
|
||||||
|
text.append(action.source.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.reason.isEmpty())
|
||||||
|
{
|
||||||
|
text.append(".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text.append(QString(": \"%1\".").arg(action.reason));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (action.reason.isEmpty())
|
if (action.isBan())
|
||||||
{
|
{
|
||||||
text = QString("%1 timed out %2 for %3.") //
|
if (action.reason.isEmpty())
|
||||||
.arg(action.source.name)
|
{
|
||||||
.arg(action.target.name)
|
text = QString("%1 banned %2.") //
|
||||||
.arg(formatTime(action.duration));
|
.arg(action.source.name)
|
||||||
|
.arg(action.target.name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = QString("%1 banned %2: \"%3\".") //
|
||||||
|
.arg(action.source.name)
|
||||||
|
.arg(action.target.name)
|
||||||
|
.arg(action.reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
text = QString("%1 timed out %2 for %3: \"%4\".") //
|
if (action.reason.isEmpty())
|
||||||
.arg(action.source.name)
|
{
|
||||||
.arg(action.target.name)
|
text = QString("%1 timed out %2 for %3.") //
|
||||||
.arg(formatTime(action.duration))
|
.arg(action.source.name)
|
||||||
.arg(action.reason);
|
.arg(action.target.name)
|
||||||
}
|
.arg(formatTime(action.duration));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = QString("%1 timed out %2 for %3: \"%4\".") //
|
||||||
|
.arg(action.source.name)
|
||||||
|
.arg(action.target.name)
|
||||||
|
.arg(formatTime(action.duration))
|
||||||
|
.arg(action.reason);
|
||||||
|
}
|
||||||
|
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
{
|
{
|
||||||
text.append(QString(" (%1 times)").arg(count));
|
text.append(QString(" (%1 times)").arg(count));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/moderationactions/ModerationActions.hpp"
|
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/layouts/MessageLayoutContainer.hpp"
|
#include "messages/layouts/MessageLayoutContainer.hpp"
|
||||||
@@ -165,21 +164,6 @@ MessageLayoutElement *EmoteElement::makeImageLayoutElement(
|
|||||||
return new ImageLayoutElement(*this, image, size);
|
return new ImageLayoutElement(*this, image, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MOD BADGE
|
|
||||||
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
|
|
||||||
MessageElementFlags flags_)
|
|
||||||
: EmoteElement(data, flags_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
|
|
||||||
const ImagePtr &image, const QSize &size)
|
|
||||||
{
|
|
||||||
static const QColor modBadgeBackgroundColor("#34AE0A");
|
|
||||||
return new ImageWithBackgroundLayoutElement(*this, image, size,
|
|
||||||
modBadgeBackgroundColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// BADGE
|
// BADGE
|
||||||
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
|
BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags)
|
||||||
: MessageElement(flags)
|
: MessageElement(flags)
|
||||||
@@ -201,8 +185,7 @@ void BadgeElement::addToContainer(MessageLayoutContainer &container,
|
|||||||
auto size = QSize(int(container.getScale() * image->width()),
|
auto size = QSize(int(container.getScale() * image->width()),
|
||||||
int(container.getScale() * image->height()));
|
int(container.getScale() * image->height()));
|
||||||
|
|
||||||
container.addElement((new ImageLayoutElement(*this, image, size))
|
container.addElement(this->makeImageLayoutElement(image, size));
|
||||||
->setLink(this->getLink()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +194,34 @@ EmotePtr BadgeElement::getEmote() const
|
|||||||
return this->emote_;
|
return this->emote_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageLayoutElement *BadgeElement::makeImageLayoutElement(
|
||||||
|
const ImagePtr &image, const QSize &size)
|
||||||
|
{
|
||||||
|
auto element =
|
||||||
|
(new ImageLayoutElement(*this, image, size))->setLink(this->getLink());
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MOD BADGE
|
||||||
|
ModBadgeElement::ModBadgeElement(const EmotePtr &data,
|
||||||
|
MessageElementFlags flags_)
|
||||||
|
: BadgeElement(data, flags_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageLayoutElement *ModBadgeElement::makeImageLayoutElement(
|
||||||
|
const ImagePtr &image, const QSize &size)
|
||||||
|
{
|
||||||
|
static const QColor modBadgeBackgroundColor("#34AE0A");
|
||||||
|
|
||||||
|
auto element = (new ImageWithBackgroundLayoutElement(
|
||||||
|
*this, image, size, modBadgeBackgroundColor))
|
||||||
|
->setLink(this->getLink());
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
// TEXT
|
// TEXT
|
||||||
TextElement::TextElement(const QString &text, MessageElementFlags flags,
|
TextElement::TextElement(const QString &text, MessageElementFlags flags,
|
||||||
const MessageColor &color, FontStyle style)
|
const MessageColor &color, FontStyle style)
|
||||||
@@ -374,8 +385,8 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
|
|||||||
{
|
{
|
||||||
QSize size(int(container.getScale() * 16),
|
QSize size(int(container.getScale() * 16),
|
||||||
int(container.getScale() * 16));
|
int(container.getScale() * 16));
|
||||||
|
auto actions = getCSettings().moderationActions.readOnly();
|
||||||
for (const auto &action : getApp()->moderationActions->items)
|
for (const auto &action : *actions)
|
||||||
{
|
{
|
||||||
if (auto image = action.getImage())
|
if (auto image = action.getImage())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -223,17 +223,6 @@ private:
|
|||||||
EmotePtr emote_;
|
EmotePtr emote_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Behaves like an emote element, except it creates a different image layout element that draws the mod badge background
|
|
||||||
class ModBadgeElement : public EmoteElement
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
|
||||||
const QSize &size) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BadgeElement : public MessageElement
|
class BadgeElement : public MessageElement
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -244,10 +233,24 @@ public:
|
|||||||
|
|
||||||
EmotePtr getEmote() const;
|
EmotePtr getEmote() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||||
|
const QSize &size);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmotePtr emote_;
|
EmotePtr emote_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ModBadgeElement : public BadgeElement
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ModBadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MessageLayoutElement *makeImageLayoutElement(const ImagePtr &image,
|
||||||
|
const QSize &size) override;
|
||||||
|
};
|
||||||
|
|
||||||
// contains a text, formated depending on the preferences
|
// contains a text, formated depending on the preferences
|
||||||
class TimestampElement : public MessageElement
|
class TimestampElement : public MessageElement
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
|||||||
Selection & /*selection*/)
|
Selection & /*selection*/)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
auto settings = getSettings();
|
||||||
|
|
||||||
QPainter painter(buffer);
|
QPainter painter(buffer);
|
||||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
@@ -296,6 +297,14 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
|||||||
backgroundColor,
|
backgroundColor,
|
||||||
*ColorProvider::instance().color(ColorType::Subscription));
|
*ColorProvider::instance().color(ColorType::Subscription));
|
||||||
}
|
}
|
||||||
|
else if (this->message_->flags.has(MessageFlag::RedeemedHighlight) &&
|
||||||
|
settings->enableRedeemedHighlight.getValue())
|
||||||
|
{
|
||||||
|
// Blend highlight color with usual background color
|
||||||
|
backgroundColor = blendColors(
|
||||||
|
backgroundColor,
|
||||||
|
*ColorProvider::instance().color(ColorType::RedeemedHighlight));
|
||||||
|
}
|
||||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||||
{
|
{
|
||||||
backgroundColor = QColor("#404040");
|
backgroundColor = QColor("#404040");
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "providers/colors/ColorProvider.hpp"
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
|
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
@@ -38,12 +37,12 @@ QSet<QColor> ColorProvider::recentColors() const
|
|||||||
* Currently, only colors used in highlight phrases are considered. This
|
* Currently, only colors used in highlight phrases are considered. This
|
||||||
* may change at any point in the future.
|
* may change at any point in the future.
|
||||||
*/
|
*/
|
||||||
for (auto phrase : getApp()->highlights->phrases)
|
for (auto phrase : getSettings()->highlightedMessages)
|
||||||
{
|
{
|
||||||
retVal.insert(*phrase.getColor());
|
retVal.insert(*phrase.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto userHl : getApp()->highlights->highlightedUsers)
|
for (auto userHl : getSettings()->highlightedUsers)
|
||||||
{
|
{
|
||||||
retVal.insert(*userHl.getColor());
|
retVal.insert(*userHl.getColor());
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,6 @@ void ColorProvider::initTypeColorMap()
|
|||||||
{
|
{
|
||||||
// Read settings for custom highlight colors and save them in map.
|
// Read settings for custom highlight colors and save them in map.
|
||||||
// If no custom values can be found, set up default values instead.
|
// If no custom values can be found, set up default values instead.
|
||||||
auto backgrounds = getApp()->themes->messages.backgrounds;
|
|
||||||
|
|
||||||
QString customColor = getSettings()->selfHighlightColor;
|
QString customColor = getSettings()->selfHighlightColor;
|
||||||
if (QColor(customColor).isValid())
|
if (QColor(customColor).isValid())
|
||||||
@@ -77,7 +75,8 @@ void ColorProvider::initTypeColorMap()
|
|||||||
{
|
{
|
||||||
this->typeColorMap_.insert(
|
this->typeColorMap_.insert(
|
||||||
{ColorType::SelfHighlight,
|
{ColorType::SelfHighlight,
|
||||||
std::make_shared<QColor>(backgrounds.highlighted)});
|
std::make_shared<QColor>(
|
||||||
|
HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR)});
|
||||||
}
|
}
|
||||||
|
|
||||||
customColor = getSettings()->subHighlightColor;
|
customColor = getSettings()->subHighlightColor;
|
||||||
@@ -90,7 +89,7 @@ void ColorProvider::initTypeColorMap()
|
|||||||
{
|
{
|
||||||
this->typeColorMap_.insert(
|
this->typeColorMap_.insert(
|
||||||
{ColorType::Subscription,
|
{ColorType::Subscription,
|
||||||
std::make_shared<QColor>(backgrounds.subscription)});
|
std::make_shared<QColor>(HighlightPhrase::FALLBACK_SUB_COLOR)});
|
||||||
}
|
}
|
||||||
|
|
||||||
customColor = getSettings()->whisperHighlightColor;
|
customColor = getSettings()->whisperHighlightColor;
|
||||||
@@ -103,22 +102,41 @@ void ColorProvider::initTypeColorMap()
|
|||||||
{
|
{
|
||||||
this->typeColorMap_.insert(
|
this->typeColorMap_.insert(
|
||||||
{ColorType::Whisper,
|
{ColorType::Whisper,
|
||||||
std::make_shared<QColor>(backgrounds.highlighted)});
|
std::make_shared<QColor>(
|
||||||
|
HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR)});
|
||||||
|
}
|
||||||
|
|
||||||
|
customColor = getSettings()->redeemedHighlightColor;
|
||||||
|
if (QColor(customColor).isValid())
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert({ColorType::RedeemedHighlight,
|
||||||
|
std::make_shared<QColor>(customColor)});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::RedeemedHighlight,
|
||||||
|
std::make_shared<QColor>(
|
||||||
|
HighlightPhrase::FALLBACK_REDEEMED_HIGHLIGHT_COLOR)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorProvider::initDefaultColors()
|
void ColorProvider::initDefaultColors()
|
||||||
{
|
{
|
||||||
// Init default colors
|
// Init default colors
|
||||||
this->defaultColors_.emplace_back(31, 141, 43, 127); // Green-ish
|
this->defaultColors_.emplace_back(75, 127, 107, 100); // Teal
|
||||||
this->defaultColors_.emplace_back(28, 126, 141, 127); // Blue-ish
|
this->defaultColors_.emplace_back(105, 127, 63, 100); // Olive
|
||||||
this->defaultColors_.emplace_back(136, 141, 49, 127); // Golden-ish
|
this->defaultColors_.emplace_back(63, 83, 127, 100); // Blue
|
||||||
this->defaultColors_.emplace_back(143, 48, 24, 127); // Red-ish
|
this->defaultColors_.emplace_back(72, 127, 63, 100); // Green
|
||||||
this->defaultColors_.emplace_back(28, 141, 117, 127); // Cyan-ish
|
|
||||||
|
|
||||||
auto backgrounds = getApp()->themes->messages.backgrounds;
|
this->defaultColors_.emplace_back(31, 141, 43, 115); // Green
|
||||||
this->defaultColors_.push_back(backgrounds.highlighted);
|
this->defaultColors_.emplace_back(28, 126, 141, 90); // Blue
|
||||||
this->defaultColors_.push_back(backgrounds.subscription);
|
this->defaultColors_.emplace_back(136, 141, 49, 90); // Golden
|
||||||
|
this->defaultColors_.emplace_back(143, 48, 24, 127); // Red
|
||||||
|
this->defaultColors_.emplace_back(28, 141, 117, 90); // Cyan
|
||||||
|
|
||||||
|
this->defaultColors_.push_back(HighlightPhrase::FALLBACK_HIGHLIGHT_COLOR);
|
||||||
|
this->defaultColors_.push_back(HighlightPhrase::FALLBACK_SUB_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
enum class ColorType { SelfHighlight, Subscription, Whisper };
|
enum class ColorType {
|
||||||
|
SelfHighlight,
|
||||||
|
Subscription,
|
||||||
|
Whisper,
|
||||||
|
RedeemedHighlight
|
||||||
|
};
|
||||||
|
|
||||||
class ColorProvider
|
class ColorProvider
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ namespace {
|
|||||||
modBadge = std::make_shared<Emote>(Emote{
|
modBadge = std::make_shared<Emote>(Emote{
|
||||||
{""},
|
{""},
|
||||||
modBadgeImageSet,
|
modBadgeImageSet,
|
||||||
Tooltip{"Twitch Channel Moderator"},
|
Tooltip{"Moderator"},
|
||||||
modBadge1x,
|
modBadge1x,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,30 @@ AbstractIrcServer::AbstractIrcServer()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AbstractIrcServer::initializeIrc()
|
||||||
|
{
|
||||||
|
assert(!this->initialized_);
|
||||||
|
|
||||||
|
if (this->hasSeparateWriteConnection())
|
||||||
|
{
|
||||||
|
this->initializeConnectionSignals(this->writeConnection_.get(),
|
||||||
|
ConnectionType::Write);
|
||||||
|
this->initializeConnectionSignals(this->readConnection_.get(),
|
||||||
|
ConnectionType::Read);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->initializeConnectionSignals(this->readConnection_.get(),
|
||||||
|
ConnectionType::Both);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->initialized_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
void AbstractIrcServer::connect()
|
void AbstractIrcServer::connect()
|
||||||
{
|
{
|
||||||
|
assert(this->initialized_);
|
||||||
|
|
||||||
this->disconnect();
|
this->disconnect();
|
||||||
|
|
||||||
if (this->hasSeparateWriteConnection())
|
if (this->hasSeparateWriteConnection())
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ public:
|
|||||||
|
|
||||||
virtual ~AbstractIrcServer() = default;
|
virtual ~AbstractIrcServer() = default;
|
||||||
|
|
||||||
|
// initializeIrc must be called from the derived class
|
||||||
|
// this allows us to initialize the abstract irc server based on the derived class's parameters
|
||||||
|
void initializeIrc();
|
||||||
|
|
||||||
// connection
|
// connection
|
||||||
void connect();
|
void connect();
|
||||||
void disconnect();
|
void disconnect();
|
||||||
@@ -45,8 +49,15 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
AbstractIrcServer();
|
AbstractIrcServer();
|
||||||
|
|
||||||
|
// initializeConnectionSignals is called on a connection once in its lifetime.
|
||||||
|
// it can be used to connect signals to your class
|
||||||
|
virtual void initializeConnectionSignals(IrcConnection *connection,
|
||||||
|
ConnectionType type){};
|
||||||
|
|
||||||
|
// initializeConnection is called every time before we try to connect to the irc server
|
||||||
virtual void initializeConnection(IrcConnection *connection,
|
virtual void initializeConnection(IrcConnection *connection,
|
||||||
ConnectionType type) = 0;
|
ConnectionType type) = 0;
|
||||||
|
|
||||||
virtual std::shared_ptr<Channel> createChannel(
|
virtual std::shared_ptr<Channel> createChannel(
|
||||||
const QString &channelName) = 0;
|
const QString &channelName) = 0;
|
||||||
|
|
||||||
@@ -83,6 +94,8 @@ private:
|
|||||||
|
|
||||||
// bool autoReconnect_ = false;
|
// bool autoReconnect_ = false;
|
||||||
pajlada::Signals::SignalHolder connections_;
|
pajlada::Signals::SignalHolder connections_;
|
||||||
|
|
||||||
|
bool initialized_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ Irc::Irc()
|
|||||||
QAbstractTableModel *Irc::newConnectionModel(QObject *parent)
|
QAbstractTableModel *Irc::newConnectionModel(QObject *parent)
|
||||||
{
|
{
|
||||||
auto model = new Model(parent);
|
auto model = new Model(parent);
|
||||||
model->init(&this->connections);
|
model->initialize(&this->connections);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ void Irc::load()
|
|||||||
{
|
{
|
||||||
ids.insert(data.id);
|
ids.insert(data.id);
|
||||||
|
|
||||||
this->connections.appendItem(data);
|
this->connections.append(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
static inline void *const noEraseCredentialCaller =
|
static inline void *const noEraseCredentialCaller =
|
||||||
reinterpret_cast<void *>(1);
|
reinterpret_cast<void *>(1);
|
||||||
|
|
||||||
UnsortedSignalVector<IrcServerData> connections;
|
SignalVector<IrcServerData> connections;
|
||||||
QAbstractTableModel *newConnectionModel(QObject *parent);
|
QAbstractTableModel *newConnectionModel(QObject *parent);
|
||||||
|
|
||||||
ChannelPtr getOrAddChannel(int serverId, QString name);
|
ChannelPtr getOrAddChannel(int serverId, QString name);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace chatterino {
|
|||||||
IrcServer::IrcServer(const IrcServerData &data)
|
IrcServer::IrcServer(const IrcServerData &data)
|
||||||
: data_(new IrcServerData(data))
|
: data_(new IrcServerData(data))
|
||||||
{
|
{
|
||||||
|
this->initializeIrc();
|
||||||
|
|
||||||
this->connect();
|
this->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,6 +53,57 @@ const QString &IrcServer::nick()
|
|||||||
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
|
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IrcServer::initializeConnectionSignals(IrcConnection *connection,
|
||||||
|
ConnectionType type)
|
||||||
|
{
|
||||||
|
assert(type == Both);
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
connection, &Communi::IrcConnection::socketError, this,
|
||||||
|
[this](QAbstractSocket::SocketError error) {
|
||||||
|
static int index =
|
||||||
|
QAbstractSocket::staticMetaObject.indexOfEnumerator(
|
||||||
|
"SocketError");
|
||||||
|
|
||||||
|
std::lock_guard lock(this->channelMutex);
|
||||||
|
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
{
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
{
|
||||||
|
shared->addMessage(makeSystemMessage(
|
||||||
|
QStringLiteral("Socket error: ") +
|
||||||
|
QAbstractSocket::staticMetaObject.enumerator(index)
|
||||||
|
.valueToKey(error)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
|
||||||
|
this, [](const QString &reserved, QString *result) {
|
||||||
|
*result = reserved + (std::rand() % 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
|
||||||
|
this, [this](Communi::IrcNoticeMessage *message) {
|
||||||
|
MessageBuilder builder;
|
||||||
|
|
||||||
|
builder.emplace<TimestampElement>();
|
||||||
|
builder.emplace<TextElement>(
|
||||||
|
message->nick(), MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(
|
||||||
|
"-> you:", MessageElementFlag::Username);
|
||||||
|
builder.emplace<TextElement>(message->content(),
|
||||||
|
MessageElementFlag::Text);
|
||||||
|
|
||||||
|
auto msg = builder.release();
|
||||||
|
|
||||||
|
for (auto &&weak : this->channels)
|
||||||
|
if (auto shared = weak.lock())
|
||||||
|
shared->addMessage(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void IrcServer::initializeConnection(IrcConnection *connection,
|
void IrcServer::initializeConnection(IrcConnection *connection,
|
||||||
ConnectionType type)
|
ConnectionType type)
|
||||||
{
|
{
|
||||||
@@ -90,47 +143,6 @@ void IrcServer::initializeConnection(IrcConnection *connection,
|
|||||||
this->open(Both);
|
this->open(Both);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
connection, &Communi::IrcConnection::socketError, this,
|
|
||||||
[this](QAbstractSocket::SocketError error) {
|
|
||||||
static int index =
|
|
||||||
QAbstractSocket::staticMetaObject.indexOfEnumerator(
|
|
||||||
"SocketError");
|
|
||||||
|
|
||||||
std::lock_guard lock(this->channelMutex);
|
|
||||||
|
|
||||||
for (auto &&weak : this->channels)
|
|
||||||
if (auto shared = weak.lock())
|
|
||||||
shared->addMessage(makeSystemMessage(
|
|
||||||
QStringLiteral("Socket error: ") +
|
|
||||||
QAbstractSocket::staticMetaObject.enumerator(index)
|
|
||||||
.valueToKey(error)));
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
|
|
||||||
this, [](const QString &reserved, QString *result) {
|
|
||||||
*result = reserved + (std::rand() % 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
|
|
||||||
this, [this](Communi::IrcNoticeMessage *message) {
|
|
||||||
MessageBuilder builder;
|
|
||||||
|
|
||||||
builder.emplace<TimestampElement>();
|
|
||||||
builder.emplace<TextElement>(
|
|
||||||
message->nick(), MessageElementFlag::Username);
|
|
||||||
builder.emplace<TextElement>(
|
|
||||||
"-> you:", MessageElementFlag::Username);
|
|
||||||
builder.emplace<TextElement>(message->content(),
|
|
||||||
MessageElementFlag::Text);
|
|
||||||
|
|
||||||
auto msg = builder.release();
|
|
||||||
|
|
||||||
for (auto &&weak : this->channels)
|
|
||||||
if (auto shared = weak.lock())
|
|
||||||
shared->addMessage(msg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Channel> IrcServer::createChannel(const QString &channelName)
|
std::shared_ptr<Channel> IrcServer::createChannel(const QString &channelName)
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ public:
|
|||||||
|
|
||||||
// AbstractIrcServer interface
|
// AbstractIrcServer interface
|
||||||
protected:
|
protected:
|
||||||
|
void initializeConnectionSignals(IrcConnection *connection,
|
||||||
|
ConnectionType type) override;
|
||||||
void initializeConnection(IrcConnection *connection,
|
void initializeConnection(IrcConnection *connection,
|
||||||
ConnectionType type) override;
|
ConnectionType type) override;
|
||||||
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
|
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
#include "ChatroomChannel.hpp"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include "TwitchApi.hpp"
|
|
||||||
#include "common/Common.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
|
||||||
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
|
||||||
#include "singletons/Emotes.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
ChatroomChannel::ChatroomChannel(const QString &channelName,
|
|
||||||
TwitchBadges &globalTwitchBadges,
|
|
||||||
BttvEmotes &globalBttv, FfzEmotes &globalFfz)
|
|
||||||
: TwitchChannel(channelName, globalTwitchBadges, globalBttv, globalFfz)
|
|
||||||
{
|
|
||||||
auto listRef = channelName.splitRef(":");
|
|
||||||
if (listRef.size() > 2)
|
|
||||||
{
|
|
||||||
this->chatroomOwnerId = listRef[1].toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChatroomChannel::refreshBTTVChannelEmotes()
|
|
||||||
{
|
|
||||||
if (this->chatroomOwnerId.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TwitchApi::findUserName(
|
|
||||||
this->chatroomOwnerId,
|
|
||||||
[this, weak = weakOf<Channel>(this)](QString username) {
|
|
||||||
BttvEmotes::loadChannel(username, [this, weak](auto &&emoteMap) {
|
|
||||||
if (auto shared = weak.lock())
|
|
||||||
this->bttvEmotes_.set(
|
|
||||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
|
||||||
});
|
|
||||||
if (auto shared = weak.lock())
|
|
||||||
{
|
|
||||||
this->chatroomOwnerName = username;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
void ChatroomChannel::refreshFFZChannelEmotes()
|
|
||||||
{
|
|
||||||
if (this->chatroomOwnerId.isEmpty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FfzEmotes::loadChannel(
|
|
||||||
this->chatroomOwnerId,
|
|
||||||
[this](auto &&emoteMap) {
|
|
||||||
this->ffzEmotes_.set(
|
|
||||||
std::make_shared<EmoteMap>(std::move(emoteMap)));
|
|
||||||
},
|
|
||||||
[this](auto &&modBadge) {
|
|
||||||
this->ffzCustomModBadge_.set(std::move(modBadge));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &ChatroomChannel::getDisplayName() const
|
|
||||||
{
|
|
||||||
return this->chatroomOwnerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "TwitchChannel.hpp"
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class ChatroomChannel : public TwitchChannel
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
explicit ChatroomChannel(const QString &channelName,
|
|
||||||
TwitchBadges &globalTwitchBadges,
|
|
||||||
BttvEmotes &globalBttv, FfzEmotes &globalFfz);
|
|
||||||
virtual void refreshBTTVChannelEmotes() override;
|
|
||||||
virtual void refreshFFZChannelEmotes() override;
|
|
||||||
virtual const QString &getDisplayName() const override;
|
|
||||||
|
|
||||||
QString chatroomOwnerId;
|
|
||||||
QString chatroomOwnerName;
|
|
||||||
|
|
||||||
friend class TwitchIrcServer;
|
|
||||||
friend class TwitchMessageBuilder;
|
|
||||||
friend class IrcMessageHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
|
||||||
#include "messages/LimitedQueue.hpp"
|
#include "messages/LimitedQueue.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||||
@@ -241,7 +240,6 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
|||||||
if (highlighted)
|
if (highlighted)
|
||||||
{
|
{
|
||||||
server.mentionsChannel->addMessage(msg);
|
server.mentionsChannel->addMessage(msg);
|
||||||
getApp()->highlights->addHighlight(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +451,6 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
|||||||
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
qDebug() << "Received whisper!";
|
|
||||||
MessageParseArgs args;
|
MessageParseArgs args;
|
||||||
|
|
||||||
args.isReceivedWhisper = true;
|
args.isReceivedWhisper = true;
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
|
||||||
#include "common/NetworkRequest.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
PartialTwitchUser PartialTwitchUser::byName(const QString &username)
|
|
||||||
{
|
|
||||||
PartialTwitchUser user;
|
|
||||||
user.username_ = username;
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
PartialTwitchUser PartialTwitchUser::byId(const QString &id)
|
|
||||||
{
|
|
||||||
PartialTwitchUser user;
|
|
||||||
user.id_ = id;
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
|
||||||
const QObject *caller)
|
|
||||||
{
|
|
||||||
getId(
|
|
||||||
successCallback, [] {}, caller);
|
|
||||||
}
|
|
||||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
|
||||||
std::function<void()> failureCallback,
|
|
||||||
const QObject *caller)
|
|
||||||
{
|
|
||||||
assert(!this->username_.isEmpty());
|
|
||||||
|
|
||||||
NetworkRequest("https://api.twitch.tv/kraken/users?login=" +
|
|
||||||
this->username_)
|
|
||||||
.caller(caller)
|
|
||||||
.authorizeTwitchV5(getDefaultClientID())
|
|
||||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
|
||||||
auto root = result.parseJson();
|
|
||||||
if (!root.value("users").isArray())
|
|
||||||
{
|
|
||||||
qDebug()
|
|
||||||
<< "API Error while getting user id, users is not an array";
|
|
||||||
failureCallback();
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto users = root.value("users").toArray();
|
|
||||||
if (users.size() != 1)
|
|
||||||
{
|
|
||||||
qDebug() << "API Error while getting user id, users array size "
|
|
||||||
"is not 1";
|
|
||||||
failureCallback();
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
if (!users[0].isObject())
|
|
||||||
{
|
|
||||||
qDebug() << "API Error while getting user id, first user is "
|
|
||||||
"not an object";
|
|
||||||
failureCallback();
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
auto firstUser = users[0].toObject();
|
|
||||||
auto id = firstUser.value("_id");
|
|
||||||
if (!id.isString())
|
|
||||||
{
|
|
||||||
qDebug() << "API Error: while getting user id, first user "
|
|
||||||
"object `_id` key is not a string";
|
|
||||||
failureCallback();
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
successCallback(id.toString());
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
// Experimental class to test a method of calling APIs on twitch users
|
|
||||||
class PartialTwitchUser
|
|
||||||
{
|
|
||||||
PartialTwitchUser() = default;
|
|
||||||
|
|
||||||
QString username_;
|
|
||||||
QString id_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static PartialTwitchUser byName(const QString &username);
|
|
||||||
static PartialTwitchUser byId(const QString &id);
|
|
||||||
|
|
||||||
void getId(std::function<void(QString)> successCallback,
|
|
||||||
const QObject *caller = nullptr);
|
|
||||||
|
|
||||||
void getId(std::function<void(QString)> successCallback,
|
|
||||||
std::function<void()> failureCallback,
|
|
||||||
const QObject *caller = nullptr);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -820,14 +820,12 @@ void PubSub::listen(rapidjson::Document &&msg)
|
|||||||
|
|
||||||
this->addClient();
|
this->addClient();
|
||||||
|
|
||||||
qDebug() << "Added to the back of the queue";
|
|
||||||
this->requests.emplace_back(
|
this->requests.emplace_back(
|
||||||
std::make_unique<rapidjson::Document>(std::move(msg)));
|
std::make_unique<rapidjson::Document>(std::move(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSub::tryListen(rapidjson::Document &msg)
|
bool PubSub::tryListen(rapidjson::Document &msg)
|
||||||
{
|
{
|
||||||
qDebug() << "tryListen with" << this->clients.size() << "clients";
|
|
||||||
for (const auto &p : this->clients)
|
for (const auto &p : this->clients)
|
||||||
{
|
{
|
||||||
const auto &client = p.second;
|
const auto &client = p.second;
|
||||||
@@ -1064,7 +1062,6 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << "Invalid whisper type:" << whisperType.c_str();
|
qDebug() << "Invalid whisper type:" << whisperType.c_str();
|
||||||
assert(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
|
||||||
@@ -151,12 +151,14 @@ void TwitchAccount::ignore(
|
|||||||
const QString &targetName,
|
const QString &targetName,
|
||||||
std::function<void(IgnoreResult, const QString &)> onFinished)
|
std::function<void(IgnoreResult, const QString &)> onFinished)
|
||||||
{
|
{
|
||||||
const auto onIdFetched = [this, targetName,
|
const auto onUserFetched = [this, targetName,
|
||||||
onFinished](QString targetUserId) {
|
onFinished](const auto &user) {
|
||||||
this->ignoreByID(targetUserId, targetName, onFinished); //
|
this->ignoreByID(user.id, targetName, onFinished); //
|
||||||
};
|
};
|
||||||
|
|
||||||
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
const auto onUserFetchFailed = [] {};
|
||||||
|
|
||||||
|
getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::ignoreByID(
|
void TwitchAccount::ignoreByID(
|
||||||
@@ -226,12 +228,14 @@ void TwitchAccount::unignore(
|
|||||||
const QString &targetName,
|
const QString &targetName,
|
||||||
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
std::function<void(UnignoreResult, const QString &message)> onFinished)
|
||||||
{
|
{
|
||||||
const auto onIdFetched = [this, targetName,
|
const auto onUserFetched = [this, targetName,
|
||||||
onFinished](QString targetUserId) {
|
onFinished](const auto &user) {
|
||||||
this->unignoreByID(targetUserId, targetName, onFinished); //
|
this->unignoreByID(user.id, targetName, onFinished); //
|
||||||
};
|
};
|
||||||
|
|
||||||
PartialTwitchUser::byName(targetName).getId(onIdFetched);
|
const auto onUserFetchFailed = [] {};
|
||||||
|
|
||||||
|
getHelix()->getUserByName(targetName, onUserFetched, onUserFetchFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::unignoreByID(
|
void TwitchAccount::unignoreByID(
|
||||||
@@ -270,28 +274,18 @@ void TwitchAccount::unignoreByID(
|
|||||||
void TwitchAccount::checkFollow(const QString targetUserID,
|
void TwitchAccount::checkFollow(const QString targetUserID,
|
||||||
std::function<void(FollowResult)> onFinished)
|
std::function<void(FollowResult)> onFinished)
|
||||||
{
|
{
|
||||||
QString url("https://api.twitch.tv/kraken/users/" + this->getUserId() +
|
const auto onResponse = [onFinished](bool following, const auto &record) {
|
||||||
"/follows/channels/" + targetUserID);
|
if (!following)
|
||||||
|
{
|
||||||
|
onFinished(FollowResult_NotFollowing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
NetworkRequest(url)
|
onFinished(FollowResult_Following);
|
||||||
|
};
|
||||||
|
|
||||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
getHelix()->getUserFollow(this->getUserId(), targetUserID, onResponse,
|
||||||
.onError([=](NetworkResult result) {
|
[] {});
|
||||||
if (result.status() == 203)
|
|
||||||
{
|
|
||||||
onFinished(FollowResult_NotFollowing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
onFinished(FollowResult_Failed);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onSuccess([=](auto result) -> Outcome {
|
|
||||||
auto document = result.parseRapidJson();
|
|
||||||
onFinished(FollowResult_Following);
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchAccount::followUser(const QString userID,
|
void TwitchAccount::followUser(const QString userID,
|
||||||
@@ -392,7 +386,6 @@ void TwitchAccount::autoModAllow(const QString msgID)
|
|||||||
QString url("https://api.twitch.tv/kraken/chat/twitchbot/approve");
|
QString url("https://api.twitch.tv/kraken/chat/twitchbot/approve");
|
||||||
|
|
||||||
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
||||||
qDebug() << qba;
|
|
||||||
|
|
||||||
NetworkRequest(url, NetworkRequestType::Post)
|
NetworkRequest(url, NetworkRequestType::Post)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
@@ -412,7 +405,6 @@ void TwitchAccount::autoModDeny(const QString msgID)
|
|||||||
QString url("https://api.twitch.tv/kraken/chat/twitchbot/deny");
|
QString url("https://api.twitch.tv/kraken/chat/twitchbot/deny");
|
||||||
|
|
||||||
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
||||||
qDebug() << qba;
|
|
||||||
|
|
||||||
NetworkRequest(url, NetworkRequestType::Post)
|
NetworkRequest(url, NetworkRequestType::Post)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
|
|||||||
@@ -3,11 +3,14 @@
|
|||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
#include "providers/twitch/api/Kraken.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
TwitchAccountManager::TwitchAccountManager()
|
TwitchAccountManager::TwitchAccountManager()
|
||||||
: anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
: accounts(SharedPtrElementLess<TwitchAccount>{})
|
||||||
|
, anonymousUser_(new TwitchAccount(ANONYMOUS_USERNAME, "", "", ""))
|
||||||
{
|
{
|
||||||
this->currentUserChanged.connect([this] {
|
this->currentUserChanged.connect([this] {
|
||||||
auto currentUser = this->getCurrent();
|
auto currentUser = this->getCurrent();
|
||||||
@@ -140,6 +143,8 @@ void TwitchAccountManager::load()
|
|||||||
if (user)
|
if (user)
|
||||||
{
|
{
|
||||||
qDebug() << "Twitch user updated to" << newUsername;
|
qDebug() << "Twitch user updated to" << newUsername;
|
||||||
|
getHelix()->update(user->getOAuthClient(), user->getOAuthToken());
|
||||||
|
getKraken()->update(user->getOAuthClient(), user->getOAuthToken());
|
||||||
this->currentUser_ = user;
|
this->currentUser_ = user;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -221,7 +226,7 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
|||||||
|
|
||||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
this->accounts.insertItem(newUser);
|
this->accounts.insert(newUser);
|
||||||
|
|
||||||
return AddUserResponse::UserAdded;
|
return AddUserResponse::UserAdded;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,7 @@ public:
|
|||||||
pajlada::Signals::NoArgSignal currentUserChanged;
|
pajlada::Signals::NoArgSignal currentUserChanged;
|
||||||
pajlada::Signals::NoArgSignal userListUpdated;
|
pajlada::Signals::NoArgSignal userListUpdated;
|
||||||
|
|
||||||
SortedSignalVector<std::shared_ptr<TwitchAccount>,
|
SignalVector<std::shared_ptr<TwitchAccount>> accounts;
|
||||||
SharedPtrElementLess<TwitchAccount>>
|
|
||||||
accounts;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class AddUserResponse {
|
enum class AddUserResponse {
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
#include "providers/twitch/TwitchApi.hpp"
|
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
|
||||||
#include "common/NetworkRequest.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
void TwitchApi::findUserId(const QString user,
|
|
||||||
std::function<void(QString)> successCallback)
|
|
||||||
{
|
|
||||||
QString requestUrl("https://api.twitch.tv/kraken/users?login=" + user);
|
|
||||||
|
|
||||||
NetworkRequest(requestUrl)
|
|
||||||
|
|
||||||
.authorizeTwitchV5(getDefaultClientID())
|
|
||||||
.timeout(30000)
|
|
||||||
.onSuccess([successCallback](auto result) mutable -> Outcome {
|
|
||||||
auto root = result.parseJson();
|
|
||||||
if (!root.value("users").isArray())
|
|
||||||
{
|
|
||||||
qDebug()
|
|
||||||
<< "API Error while getting user id, users is not an array";
|
|
||||||
successCallback("");
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
auto users = root.value("users").toArray();
|
|
||||||
if (users.size() != 1)
|
|
||||||
{
|
|
||||||
qDebug() << "API Error while getting user id, users array size "
|
|
||||||
"is not 1";
|
|
||||||
successCallback("");
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
if (!users[0].isObject())
|
|
||||||
{
|
|
||||||
qDebug() << "API Error while getting user id, first user is "
|
|
||||||
"not an object";
|
|
||||||
successCallback("");
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
auto firstUser = users[0].toObject();
|
|
||||||
auto id = firstUser.value("_id");
|
|
||||||
if (!id.isString())
|
|
||||||
{
|
|
||||||
qDebug() << "API Error: while getting user id, first user "
|
|
||||||
"object `_id` key is not a string";
|
|
||||||
successCallback("");
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
successCallback(id.toString());
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwitchApi::findUserName(const QString userid,
|
|
||||||
std::function<void(QString)> successCallback)
|
|
||||||
{
|
|
||||||
QString requestUrl("https://api.twitch.tv/kraken/users/" + userid);
|
|
||||||
|
|
||||||
NetworkRequest(requestUrl)
|
|
||||||
|
|
||||||
.authorizeTwitchV5(getDefaultClientID())
|
|
||||||
.timeout(30000)
|
|
||||||
.onSuccess([successCallback](auto result) mutable -> Outcome {
|
|
||||||
auto root = result.parseJson();
|
|
||||||
auto name = root.value("name");
|
|
||||||
if (!name.isString())
|
|
||||||
{
|
|
||||||
qDebug() << "API Error: while getting user name, `name` is not "
|
|
||||||
"a string";
|
|
||||||
successCallback("");
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
successCallback(name.toString());
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class TwitchApi
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void findUserId(const QString user,
|
|
||||||
std::function<void(QString)> callback);
|
|
||||||
static void findUserName(const QString userid,
|
|
||||||
std::function<void(QString)> callback);
|
|
||||||
|
|
||||||
private:
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
#include "providers/twitch/TwitchMessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
|
#include "providers/twitch/TwitchParseCheerEmotes.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
#include "providers/twitch/api/Kraken.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
#include <IrcConnection>
|
#include <IrcConnection>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@@ -454,35 +457,25 @@ void TwitchChannel::refreshTitle()
|
|||||||
}
|
}
|
||||||
this->titleRefreshedTime_ = QTime::currentTime();
|
this->titleRefreshedTime_ = QTime::currentTime();
|
||||||
|
|
||||||
QString url("https://api.twitch.tv/kraken/channels/" + roomID);
|
const auto onSuccess = [this,
|
||||||
NetworkRequest::twitchRequest(url)
|
weak = weakOf<Channel>(this)](const auto &channel) {
|
||||||
.onSuccess(
|
ChannelPtr shared = weak.lock();
|
||||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
if (!shared)
|
||||||
ChannelPtr shared = weak.lock();
|
{
|
||||||
if (!shared)
|
return;
|
||||||
return Failure;
|
}
|
||||||
|
|
||||||
const auto document = result.parseRapidJson();
|
{
|
||||||
|
auto status = this->streamStatus_.access();
|
||||||
|
status->title = channel.status;
|
||||||
|
}
|
||||||
|
|
||||||
auto statusIt = document.FindMember("status");
|
this->liveStatusChanged.invoke();
|
||||||
|
};
|
||||||
|
|
||||||
if (statusIt == document.MemberEnd())
|
const auto onFailure = [] {};
|
||||||
{
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
getKraken()->getChannel(roomID, onSuccess, onFailure);
|
||||||
auto status = this->streamStatus_.access();
|
|
||||||
if (!rj::getSafe(statusIt->value, status->title))
|
|
||||||
{
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->liveStatusChanged.invoke();
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::refreshLiveStatus()
|
void TwitchChannel::refreshLiveStatus()
|
||||||
@@ -497,106 +490,72 @@ void TwitchChannel::refreshLiveStatus()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
getHelix()->getStreamById(
|
||||||
|
roomID,
|
||||||
|
[this, weak = weakOf<Channel>(this)](bool live, const auto &stream) {
|
||||||
|
ChannelPtr shared = weak.lock();
|
||||||
|
if (!shared)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// auto request = makeGetStreamRequest(roomID, QThread::currentThread());
|
this->parseLiveStatus(live, stream);
|
||||||
NetworkRequest::twitchRequest(url)
|
},
|
||||||
|
[] {
|
||||||
.onSuccess(
|
// failure
|
||||||
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
});
|
||||||
ChannelPtr shared = weak.lock();
|
|
||||||
if (!shared)
|
|
||||||
return Failure;
|
|
||||||
|
|
||||||
return this->parseLiveStatus(result.parseRapidJson());
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
void TwitchChannel::parseLiveStatus(bool live, const HelixStream &stream)
|
||||||
{
|
{
|
||||||
if (!document.IsObject())
|
if (!live)
|
||||||
{
|
{
|
||||||
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!document.HasMember("stream"))
|
|
||||||
{
|
|
||||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &stream = document["stream"];
|
|
||||||
|
|
||||||
if (!stream.IsObject())
|
|
||||||
{
|
|
||||||
// Stream is offline (stream is most likely null)
|
|
||||||
this->setLive(false);
|
this->setLive(false);
|
||||||
return Failure;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
|
||||||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
|
||||||
{
|
|
||||||
qDebug()
|
|
||||||
<< "[TwitchChannel:refreshLiveStatus] Missing members in stream";
|
|
||||||
this->setLive(false);
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rapidjson::Value &streamChannel = stream["channel"];
|
|
||||||
|
|
||||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
|
||||||
{
|
|
||||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
|
|
||||||
"\"status\" in channel";
|
|
||||||
return Failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream is live
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto status = this->streamStatus_.access();
|
auto status = this->streamStatus_.access();
|
||||||
status->viewerCount = stream["viewers"].GetUint();
|
status->viewerCount = stream.viewerCount;
|
||||||
status->game = stream["game"].GetString();
|
if (status->gameId != stream.gameId)
|
||||||
status->title = streamChannel["status"].GetString();
|
{
|
||||||
QDateTime since = QDateTime::fromString(
|
status->gameId = stream.gameId;
|
||||||
stream["created_at"].GetString(), Qt::ISODate);
|
|
||||||
|
// Resolve game ID to game name
|
||||||
|
getHelix()->getGameById(
|
||||||
|
stream.gameId,
|
||||||
|
[this, weak = weakOf<Channel>(this)](const auto &game) {
|
||||||
|
ChannelPtr shared = weak.lock();
|
||||||
|
if (!shared)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto status = this->streamStatus_.access();
|
||||||
|
status->game = game.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->liveStatusChanged.invoke();
|
||||||
|
},
|
||||||
|
[] {
|
||||||
|
// failure
|
||||||
|
});
|
||||||
|
}
|
||||||
|
status->title = stream.title;
|
||||||
|
QDateTime since = QDateTime::fromString(stream.startedAt, Qt::ISODate);
|
||||||
auto diff = since.secsTo(QDateTime::currentDateTime());
|
auto diff = since.secsTo(QDateTime::currentDateTime());
|
||||||
status->uptime = QString::number(diff / 3600) + "h " +
|
status->uptime = QString::number(diff / 3600) + "h " +
|
||||||
QString::number(diff % 3600 / 60) + "m";
|
QString::number(diff % 3600 / 60) + "m";
|
||||||
|
|
||||||
status->rerun = false;
|
status->rerun = false;
|
||||||
if (stream.HasMember("stream_type"))
|
status->streamType = stream.type;
|
||||||
{
|
|
||||||
status->streamType = stream["stream_type"].GetString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
status->streamType = QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream.HasMember("broadcast_platform"))
|
|
||||||
{
|
|
||||||
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
|
||||||
|
|
||||||
if (broadcastPlatformValue.IsString())
|
|
||||||
{
|
|
||||||
const char *broadcastPlatform =
|
|
||||||
stream["broadcast_platform"].GetString();
|
|
||||||
if (strcmp(broadcastPlatform, "rerun") == 0)
|
|
||||||
{
|
|
||||||
status->rerun = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setLive(true);
|
|
||||||
|
this->setLive(true);
|
||||||
|
|
||||||
// Signal all listeners that the stream status has been updated
|
// Signal all listeners that the stream status has been updated
|
||||||
this->liveStatusChanged.invoke();
|
this->liveStatusChanged.invoke();
|
||||||
|
|
||||||
return Success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchChannel::loadRecentMessages()
|
void TwitchChannel::loadRecentMessages()
|
||||||
@@ -641,9 +600,6 @@ void TwitchChannel::loadRecentMessages()
|
|||||||
|
|
||||||
void TwitchChannel::refreshPubsub()
|
void TwitchChannel::refreshPubsub()
|
||||||
{
|
{
|
||||||
// listen to moderation actions
|
|
||||||
if (!this->hasModRights())
|
|
||||||
return;
|
|
||||||
auto roomId = this->roomId();
|
auto roomId = this->roomId();
|
||||||
if (roomId.isEmpty())
|
if (roomId.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -8,14 +8,15 @@
|
|||||||
#include "common/UniqueAccess.hpp"
|
#include "common/UniqueAccess.hpp"
|
||||||
#include "common/UsernameSet.hpp"
|
#include "common/UsernameSet.hpp"
|
||||||
#include "providers/twitch/TwitchEmotes.hpp"
|
#include "providers/twitch/TwitchEmotes.hpp"
|
||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
|
||||||
#include <IrcConnection>
|
#include <IrcConnection>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
#include <mutex>
|
|
||||||
#include <pajlada/signals/signalholder.hpp>
|
#include <pajlada/signals/signalholder.hpp>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
@@ -43,6 +44,7 @@ public:
|
|||||||
unsigned viewerCount = 0;
|
unsigned viewerCount = 0;
|
||||||
QString title;
|
QString title;
|
||||||
QString game;
|
QString game;
|
||||||
|
QString gameId;
|
||||||
QString uptime;
|
QString uptime;
|
||||||
QString streamType;
|
QString streamType;
|
||||||
};
|
};
|
||||||
@@ -120,7 +122,7 @@ protected:
|
|||||||
private:
|
private:
|
||||||
// Methods
|
// Methods
|
||||||
void refreshLiveStatus();
|
void refreshLiveStatus();
|
||||||
Outcome parseLiveStatus(const rapidjson::Document &document);
|
void parseLiveStatus(bool live, const HelixStream &stream);
|
||||||
void refreshPubsub();
|
void refreshPubsub();
|
||||||
void refreshChatters();
|
void refreshChatters();
|
||||||
void refreshBadges();
|
void refreshBadges();
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/ChatroomChannel.hpp"
|
|
||||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "providers/twitch/PubsubClient.hpp"
|
#include "providers/twitch/PubsubClient.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
@@ -24,26 +22,13 @@ using namespace std::chrono_literals;
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
namespace {
|
|
||||||
bool isChatroom(const QString &channel)
|
|
||||||
{
|
|
||||||
if (channel.left(10) == "chatrooms:")
|
|
||||||
{
|
|
||||||
auto reflist = channel.splitRef(':');
|
|
||||||
if (reflist.size() == 3)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TwitchIrcServer::TwitchIrcServer()
|
TwitchIrcServer::TwitchIrcServer()
|
||||||
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
: whispersChannel(new Channel("/whispers", Channel::Type::TwitchWhispers))
|
||||||
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
, mentionsChannel(new Channel("/mentions", Channel::Type::TwitchMentions))
|
||||||
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
, watchingChannel(Channel::getEmpty(), Channel::Type::TwitchWatching)
|
||||||
{
|
{
|
||||||
|
this->initializeIrc();
|
||||||
|
|
||||||
this->pubsub = new PubSub;
|
this->pubsub = new PubSub;
|
||||||
|
|
||||||
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
// getSettings()->twitchSeperateWriteConnection.connect([this](auto, auto) {
|
||||||
@@ -100,18 +85,8 @@ void TwitchIrcServer::initializeConnection(IrcConnection *connection,
|
|||||||
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
std::shared_ptr<Channel> TwitchIrcServer::createChannel(
|
||||||
const QString &channelName)
|
const QString &channelName)
|
||||||
{
|
{
|
||||||
std::shared_ptr<TwitchChannel> channel;
|
auto channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
|
||||||
if (isChatroom(channelName))
|
channelName, this->twitchBadges, this->bttv, this->ffz));
|
||||||
{
|
|
||||||
channel = std::static_pointer_cast<TwitchChannel>(
|
|
||||||
std::shared_ptr<ChatroomChannel>(new ChatroomChannel(
|
|
||||||
channelName, this->twitchBadges, this->bttv, this->ffz)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
channel = std::shared_ptr<TwitchChannel>(new TwitchChannel(
|
|
||||||
channelName, this->twitchBadges, this->bttv, this->ffz));
|
|
||||||
}
|
|
||||||
channel->initialize();
|
channel->initialize();
|
||||||
|
|
||||||
channel->sendMessageSignal.connect(
|
channel->sendMessageSignal.connect(
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/pings/PingController.hpp"
|
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
@@ -28,7 +27,8 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const QSet<QString> zeroWidthEmotes{
|
const QSet<QString> zeroWidthEmotes{
|
||||||
"SoSnowy", "IceCold", "SantaHat", "TopHat", "ReinDeer", "CandyCane",
|
"SoSnowy", "IceCold", "SantaHat", "TopHat",
|
||||||
|
"ReinDeer", "CandyCane", "cvMask", "cvHazmat",
|
||||||
};
|
};
|
||||||
|
|
||||||
QColor getRandomColor(const QVariant &userId)
|
QColor getRandomColor(const QVariant &userId)
|
||||||
@@ -170,12 +170,11 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
// TODO(pajlada): Do we need to check if the phrase is valid first?
|
||||||
for (const auto &phrase : app->ignores->phrases)
|
auto phrases = getCSettings().ignoredMessages.readOnly();
|
||||||
|
for (const auto &phrase : *phrases)
|
||||||
{
|
{
|
||||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||||
{
|
{
|
||||||
qDebug() << "Blocking message because it contains ignored phrase"
|
|
||||||
<< phrase.getPattern();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,8 +204,7 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||||||
case ShowIgnoredUsersMessages::Never:
|
case ShowIgnoredUsersMessages::Never:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
qDebug() << "Blocking message because it's from blocked user"
|
|
||||||
<< user.name;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +236,7 @@ void TwitchMessageBuilder::triggerHighlights()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getApp()->pings->isMuted(this->channel->getName()))
|
if (getCSettings().isMutedChannel(this->channel->getName()))
|
||||||
{
|
{
|
||||||
// Do nothing. Pings are muted in this channel.
|
// Do nothing. Pings are muted in this channel.
|
||||||
return;
|
return;
|
||||||
@@ -297,6 +295,13 @@ MessagePtr TwitchMessageBuilder::build()
|
|||||||
|
|
||||||
this->historicalMessage_ = this->tags.contains("historical");
|
this->historicalMessage_ = this->tags.contains("historical");
|
||||||
|
|
||||||
|
if (this->tags.contains("msg-id") &&
|
||||||
|
this->tags["msg-id"].toString().split(';').contains(
|
||||||
|
"highlighted-message"))
|
||||||
|
{
|
||||||
|
this->message().flags.set(MessageFlag::RedeemedHighlight);
|
||||||
|
}
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
if (this->historicalMessage_)
|
if (this->historicalMessage_)
|
||||||
{
|
{
|
||||||
@@ -764,8 +769,7 @@ void TwitchMessageBuilder::appendUsername()
|
|||||||
void TwitchMessageBuilder::runIgnoreReplaces(
|
void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes)
|
std::vector<std::tuple<int, EmotePtr, EmoteName>> &twitchEmotes)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto phrases = getCSettings().ignoredMessages.readOnly();
|
||||||
const auto &phrases = app->ignores->phrases;
|
|
||||||
auto removeEmotesInRange =
|
auto removeEmotesInRange =
|
||||||
[](int pos, int len,
|
[](int pos, int len,
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>>
|
std::vector<std::tuple<int, EmotePtr, EmoteName>>
|
||||||
@@ -828,7 +832,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &phrase : phrases)
|
for (const auto &phrase : *phrases)
|
||||||
{
|
{
|
||||||
if (phrase.isBlock())
|
if (phrase.isBlock())
|
||||||
{
|
{
|
||||||
@@ -1017,7 +1021,7 @@ void TwitchMessageBuilder::parseHighlights()
|
|||||||
|
|
||||||
QString currentUsername = currentUser->getUserName();
|
QString currentUsername = currentUser->getUserName();
|
||||||
|
|
||||||
if (app->highlights->blacklistContains(this->ircMessage->nick()))
|
if (getCSettings().isBlacklistedUser(this->ircMessage->nick()))
|
||||||
{
|
{
|
||||||
// Do nothing. We ignore highlights from this user.
|
// Do nothing. We ignore highlights from this user.
|
||||||
return;
|
return;
|
||||||
@@ -1056,18 +1060,14 @@ void TwitchMessageBuilder::parseHighlights()
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<HighlightPhrase> userHighlights =
|
|
||||||
app->highlights->highlightedUsers.cloneVector();
|
|
||||||
|
|
||||||
// Highlight because of sender
|
// Highlight because of sender
|
||||||
for (const HighlightPhrase &userHighlight : userHighlights)
|
auto userHighlights = getCSettings().highlightedUsers.readOnly();
|
||||||
|
for (const HighlightPhrase &userHighlight : *userHighlights)
|
||||||
{
|
{
|
||||||
if (!userHighlight.isMatch(this->ircMessage->nick()))
|
if (!userHighlight.isMatch(this->ircMessage->nick()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
qDebug() << "Highlight because user" << this->ircMessage->nick()
|
|
||||||
<< "sent a message";
|
|
||||||
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
this->message().highlightColor = userHighlight.getColor();
|
this->message().highlightColor = userHighlight.getColor();
|
||||||
@@ -1111,7 +1111,7 @@ void TwitchMessageBuilder::parseHighlights()
|
|||||||
// TODO: This vector should only be rebuilt upon highlights being changed
|
// TODO: This vector should only be rebuilt upon highlights being changed
|
||||||
// fourtf: should be implemented in the HighlightsController
|
// fourtf: should be implemented in the HighlightsController
|
||||||
std::vector<HighlightPhrase> activeHighlights =
|
std::vector<HighlightPhrase> activeHighlights =
|
||||||
app->highlights->phrases.cloneVector();
|
getSettings()->highlightedMessages.cloneVector();
|
||||||
|
|
||||||
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0)
|
if (getSettings()->enableSelfHighlight && currentUsername.size() > 0)
|
||||||
{
|
{
|
||||||
@@ -1131,9 +1131,6 @@ void TwitchMessageBuilder::parseHighlights()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Highlight because" << this->originalMessage_ << "matches"
|
|
||||||
<< highlight.getPattern();
|
|
||||||
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
this->message().highlightColor = highlight.getColor();
|
this->message().highlightColor = highlight.getColor();
|
||||||
|
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ namespace {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// Look through the results of
|
// Look through the results of
|
||||||
|
|||||||
@@ -0,0 +1,368 @@
|
|||||||
|
#include "providers/twitch/api/Helix.hpp"
|
||||||
|
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
static Helix *instance = nullptr;
|
||||||
|
|
||||||
|
void Helix::fetchUsers(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
|
||||||
|
for (const auto &id : userIds)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &login : userLogins)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("login", login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: set on success and on error
|
||||||
|
this->makeRequest("users", urlQuery)
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
auto data = root.value("data");
|
||||||
|
|
||||||
|
if (!data.isArray())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelixUser> users;
|
||||||
|
|
||||||
|
for (const auto &jsonUser : data.toArray())
|
||||||
|
{
|
||||||
|
users.emplace_back(jsonUser.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback(users);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getUserByName(QString userId,
|
||||||
|
ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QStringList userIds;
|
||||||
|
QStringList userLogins{userId};
|
||||||
|
|
||||||
|
this->fetchUsers(
|
||||||
|
userIds, userLogins,
|
||||||
|
[successCallback,
|
||||||
|
failureCallback](const std::vector<HelixUser> &users) {
|
||||||
|
if (users.empty())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(users[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getUserById(QString userId,
|
||||||
|
ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QStringList userIds{userId};
|
||||||
|
QStringList userLogins;
|
||||||
|
|
||||||
|
this->fetchUsers(
|
||||||
|
userIds, userLogins,
|
||||||
|
[successCallback, failureCallback](const auto &users) {
|
||||||
|
if (users.empty())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(users[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::fetchUsersFollows(
|
||||||
|
QString fromId, QString toId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
assert(!fromId.isEmpty() || !toId.isEmpty());
|
||||||
|
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
|
||||||
|
if (!fromId.isEmpty())
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("from_id", fromId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toId.isEmpty())
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("to_id", toId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: set on success and on error
|
||||||
|
this->makeRequest("users/follows", urlQuery)
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
if (root.empty())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
successCallback(HelixUsersFollowsResponse(root));
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getUserFollowers(
|
||||||
|
QString userId, ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
this->fetchUsersFollows("", userId, successCallback, failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getUserFollow(
|
||||||
|
QString userId, QString targetId,
|
||||||
|
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
this->fetchUsersFollows(
|
||||||
|
userId, targetId,
|
||||||
|
[successCallback](const auto &response) {
|
||||||
|
if (response.data.empty())
|
||||||
|
{
|
||||||
|
successCallback(false, HelixUsersFollowsRecord());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback(true, response.data[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::fetchStreams(
|
||||||
|
QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
|
||||||
|
for (const auto &id : userIds)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("user_id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &login : userLogins)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("user_login", login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: set on success and on error
|
||||||
|
this->makeRequest("streams", urlQuery)
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
auto data = root.value("data");
|
||||||
|
|
||||||
|
if (!data.isArray())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelixStream> streams;
|
||||||
|
|
||||||
|
for (const auto &jsonStream : data.toArray())
|
||||||
|
{
|
||||||
|
streams.emplace_back(jsonStream.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback(streams);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getStreamById(QString userId,
|
||||||
|
ResultCallback<bool, HelixStream> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QStringList userIds{userId};
|
||||||
|
QStringList userLogins;
|
||||||
|
|
||||||
|
this->fetchStreams(
|
||||||
|
userIds, userLogins,
|
||||||
|
[successCallback, failureCallback](const auto &streams) {
|
||||||
|
if (streams.empty())
|
||||||
|
{
|
||||||
|
successCallback(false, HelixStream());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(true, streams[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getStreamByName(QString userName,
|
||||||
|
ResultCallback<bool, HelixStream> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QStringList userIds;
|
||||||
|
QStringList userLogins{userName};
|
||||||
|
|
||||||
|
this->fetchStreams(
|
||||||
|
userIds, userLogins,
|
||||||
|
[successCallback, failureCallback](const auto &streams) {
|
||||||
|
if (streams.empty())
|
||||||
|
{
|
||||||
|
successCallback(false, HelixStream());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(true, streams[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
void Helix::fetchGames(QStringList gameIds, QStringList gameNames,
|
||||||
|
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
assert((gameIds.length() + gameNames.length()) > 0);
|
||||||
|
|
||||||
|
QUrlQuery urlQuery;
|
||||||
|
|
||||||
|
for (const auto &id : gameIds)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &login : gameNames)
|
||||||
|
{
|
||||||
|
urlQuery.addQueryItem("name", login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: set on success and on error
|
||||||
|
this->makeRequest("games", urlQuery)
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
auto data = root.value("data");
|
||||||
|
|
||||||
|
if (!data.isArray())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HelixGame> games;
|
||||||
|
|
||||||
|
for (const auto &jsonStream : data.toArray())
|
||||||
|
{
|
||||||
|
games.emplace_back(jsonStream.toObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
successCallback(games);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::getGameById(QString gameId,
|
||||||
|
ResultCallback<HelixGame> successCallback,
|
||||||
|
HelixFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
QStringList gameIds{gameId};
|
||||||
|
QStringList gameNames;
|
||||||
|
|
||||||
|
this->fetchGames(
|
||||||
|
gameIds, gameNames,
|
||||||
|
[successCallback, failureCallback](const auto &games) {
|
||||||
|
if (games.empty())
|
||||||
|
{
|
||||||
|
failureCallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
successCallback(games[0]);
|
||||||
|
},
|
||||||
|
failureCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
||||||
|
{
|
||||||
|
assert(!url.startsWith("/"));
|
||||||
|
|
||||||
|
if (this->clientId.isEmpty())
|
||||||
|
{
|
||||||
|
qDebug()
|
||||||
|
<< "Helix::makeRequest called without a client ID set BabyRage";
|
||||||
|
// return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->oauthToken.isEmpty())
|
||||||
|
{
|
||||||
|
qDebug()
|
||||||
|
<< "Helix::makeRequest called without an oauth token set BabyRage";
|
||||||
|
// return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString baseUrl("https://api.twitch.tv/helix/");
|
||||||
|
|
||||||
|
QUrl fullUrl(baseUrl + url);
|
||||||
|
|
||||||
|
fullUrl.setQuery(urlQuery);
|
||||||
|
|
||||||
|
return NetworkRequest(fullUrl)
|
||||||
|
.timeout(5 * 1000)
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Client-ID", this->clientId)
|
||||||
|
.header("Authorization", "Bearer " + this->oauthToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::update(QString clientId, QString oauthToken)
|
||||||
|
{
|
||||||
|
this->clientId = clientId;
|
||||||
|
this->oauthToken = oauthToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Helix::initialize()
|
||||||
|
{
|
||||||
|
assert(instance == nullptr);
|
||||||
|
|
||||||
|
instance = new Helix();
|
||||||
|
}
|
||||||
|
|
||||||
|
Helix *getHelix()
|
||||||
|
{
|
||||||
|
assert(instance != nullptr);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/NetworkRequest.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
using HelixFailureCallback = std::function<void()>;
|
||||||
|
template <typename... T>
|
||||||
|
using ResultCallback = std::function<void(T...)>;
|
||||||
|
|
||||||
|
struct HelixUser {
|
||||||
|
QString id;
|
||||||
|
QString login;
|
||||||
|
QString displayName;
|
||||||
|
QString description;
|
||||||
|
QString profileImageUrl;
|
||||||
|
int viewCount;
|
||||||
|
|
||||||
|
explicit HelixUser(QJsonObject jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, login(jsonObject.value("login").toString())
|
||||||
|
, displayName(jsonObject.value("display_name").toString())
|
||||||
|
, description(jsonObject.value("description").toString())
|
||||||
|
, profileImageUrl(jsonObject.value("profile_image_url").toString())
|
||||||
|
, viewCount(jsonObject.value("view_count").toInt())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixUsersFollowsRecord {
|
||||||
|
QString fromId;
|
||||||
|
QString fromName;
|
||||||
|
QString toId;
|
||||||
|
QString toName;
|
||||||
|
QString followedAt; // date time object
|
||||||
|
|
||||||
|
HelixUsersFollowsRecord()
|
||||||
|
: fromId("")
|
||||||
|
, fromName("")
|
||||||
|
, toId("")
|
||||||
|
, toName("")
|
||||||
|
, followedAt("")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit HelixUsersFollowsRecord(QJsonObject jsonObject)
|
||||||
|
: fromId(jsonObject.value("from_id").toString())
|
||||||
|
, fromName(jsonObject.value("from_name").toString())
|
||||||
|
, toId(jsonObject.value("to_id").toString())
|
||||||
|
, toName(jsonObject.value("to_name").toString())
|
||||||
|
, followedAt(jsonObject.value("followed_at").toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixUsersFollowsResponse {
|
||||||
|
int total;
|
||||||
|
std::vector<HelixUsersFollowsRecord> data;
|
||||||
|
explicit HelixUsersFollowsResponse(QJsonObject jsonObject)
|
||||||
|
: total(jsonObject.value("total").toInt())
|
||||||
|
{
|
||||||
|
const auto &jsonData = jsonObject.value("data").toArray();
|
||||||
|
std::transform(jsonData.begin(), jsonData.end(),
|
||||||
|
std::back_inserter(this->data),
|
||||||
|
[](const QJsonValue &record) {
|
||||||
|
return HelixUsersFollowsRecord(record.toObject());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixStream {
|
||||||
|
QString id; // stream id
|
||||||
|
QString userId;
|
||||||
|
QString userName;
|
||||||
|
QString gameId;
|
||||||
|
QString type;
|
||||||
|
QString title;
|
||||||
|
int viewerCount;
|
||||||
|
QString startedAt;
|
||||||
|
QString language;
|
||||||
|
QString thumbnailUrl;
|
||||||
|
|
||||||
|
HelixStream()
|
||||||
|
: id("")
|
||||||
|
, userId("")
|
||||||
|
, userName("")
|
||||||
|
, gameId("")
|
||||||
|
, type("")
|
||||||
|
, title("")
|
||||||
|
, viewerCount()
|
||||||
|
, startedAt("")
|
||||||
|
, language("")
|
||||||
|
, thumbnailUrl("")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit HelixStream(QJsonObject jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, userId(jsonObject.value("user_id").toString())
|
||||||
|
, userName(jsonObject.value("user_name").toString())
|
||||||
|
, gameId(jsonObject.value("game_id").toString())
|
||||||
|
, type(jsonObject.value("type").toString())
|
||||||
|
, title(jsonObject.value("title").toString())
|
||||||
|
, viewerCount(jsonObject.value("viewer_count").toInt())
|
||||||
|
, startedAt(jsonObject.value("started_at").toString())
|
||||||
|
, language(jsonObject.value("language").toString())
|
||||||
|
, thumbnailUrl(jsonObject.value("thumbnail_url").toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HelixGame {
|
||||||
|
QString id; // stream id
|
||||||
|
QString name;
|
||||||
|
QString boxArtUrl;
|
||||||
|
|
||||||
|
explicit HelixGame(QJsonObject jsonObject)
|
||||||
|
: id(jsonObject.value("id").toString())
|
||||||
|
, name(jsonObject.value("name").toString())
|
||||||
|
, boxArtUrl(jsonObject.value("box_art_url").toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Helix final : boost::noncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-users
|
||||||
|
void fetchUsers(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixUser>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
void getUserByName(QString userName,
|
||||||
|
ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
void getUserById(QString userId, ResultCallback<HelixUser> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-users-follows
|
||||||
|
void fetchUsersFollows(
|
||||||
|
QString fromId, QString toId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void getUserFollowers(
|
||||||
|
QString userId,
|
||||||
|
ResultCallback<HelixUsersFollowsResponse> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void getUserFollow(
|
||||||
|
QString userId, QString targetId,
|
||||||
|
ResultCallback<bool, HelixUsersFollowsRecord> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-streams
|
||||||
|
void fetchStreams(QStringList userIds, QStringList userLogins,
|
||||||
|
ResultCallback<std::vector<HelixStream>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void getStreamById(QString userId,
|
||||||
|
ResultCallback<bool, HelixStream> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void getStreamByName(QString userName,
|
||||||
|
ResultCallback<bool, HelixStream> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/api/reference#get-games
|
||||||
|
void fetchGames(QStringList gameIds, QStringList gameNames,
|
||||||
|
ResultCallback<std::vector<HelixGame>> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void getGameById(QString gameId, ResultCallback<HelixGame> successCallback,
|
||||||
|
HelixFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void update(QString clientId, QString oauthToken);
|
||||||
|
|
||||||
|
static void initialize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
|
||||||
|
|
||||||
|
QString clientId;
|
||||||
|
QString oauthToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
Helix *getHelix();
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#include "providers/twitch/api/Kraken.hpp"
|
||||||
|
|
||||||
|
#include "common/Outcome.hpp"
|
||||||
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
static Kraken *instance = nullptr;
|
||||||
|
|
||||||
|
void Kraken::getChannel(QString userId,
|
||||||
|
ResultCallback<KrakenChannel> successCallback,
|
||||||
|
KrakenFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
assert(!userId.isEmpty());
|
||||||
|
|
||||||
|
this->makeRequest("channels/" + userId, {})
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
|
||||||
|
successCallback(root);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kraken::getUser(QString userId, ResultCallback<KrakenUser> successCallback,
|
||||||
|
KrakenFailureCallback failureCallback)
|
||||||
|
{
|
||||||
|
assert(!userId.isEmpty());
|
||||||
|
|
||||||
|
this->makeRequest("users/" + userId, {})
|
||||||
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
|
auto root = result.parseJson();
|
||||||
|
|
||||||
|
successCallback(root);
|
||||||
|
|
||||||
|
return Success;
|
||||||
|
})
|
||||||
|
.onError([failureCallback](auto result) {
|
||||||
|
// TODO: make better xd
|
||||||
|
failureCallback();
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkRequest Kraken::makeRequest(QString url, QUrlQuery urlQuery)
|
||||||
|
{
|
||||||
|
assert(!url.startsWith("/"));
|
||||||
|
|
||||||
|
if (this->clientId.isEmpty())
|
||||||
|
{
|
||||||
|
qDebug()
|
||||||
|
<< "Kraken::makeRequest called without a client ID set BabyRage";
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString baseUrl("https://api.twitch.tv/kraken/");
|
||||||
|
|
||||||
|
QUrl fullUrl(baseUrl + url);
|
||||||
|
|
||||||
|
fullUrl.setQuery(urlQuery);
|
||||||
|
|
||||||
|
if (!this->oauthToken.isEmpty())
|
||||||
|
{
|
||||||
|
return NetworkRequest(fullUrl)
|
||||||
|
.timeout(5 * 1000)
|
||||||
|
.header("Accept", "application/vnd.twitchtv.v5+json")
|
||||||
|
.header("Client-ID", this->clientId)
|
||||||
|
.header("Authorization", "OAuth " + this->oauthToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetworkRequest(fullUrl)
|
||||||
|
.timeout(5 * 1000)
|
||||||
|
.header("Accept", "application/vnd.twitchtv.v5+json")
|
||||||
|
.header("Client-ID", this->clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kraken::update(QString clientId, QString oauthToken)
|
||||||
|
{
|
||||||
|
this->clientId = clientId;
|
||||||
|
this->oauthToken = oauthToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kraken::initialize()
|
||||||
|
{
|
||||||
|
assert(instance == nullptr);
|
||||||
|
|
||||||
|
instance = new Kraken();
|
||||||
|
|
||||||
|
getKraken()->update(getDefaultClientID(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Kraken *getKraken()
|
||||||
|
{
|
||||||
|
assert(instance != nullptr);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/NetworkRequest.hpp"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
using KrakenFailureCallback = std::function<void()>;
|
||||||
|
template <typename... T>
|
||||||
|
using ResultCallback = std::function<void(T...)>;
|
||||||
|
|
||||||
|
struct KrakenChannel {
|
||||||
|
const QString status;
|
||||||
|
|
||||||
|
KrakenChannel(QJsonObject jsonObject)
|
||||||
|
: status(jsonObject.value("status").toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KrakenUser {
|
||||||
|
const QString createdAt;
|
||||||
|
|
||||||
|
KrakenUser(QJsonObject jsonObject)
|
||||||
|
: createdAt(jsonObject.value("created_at").toString())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Kraken final : boost::noncopyable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// https://dev.twitch.tv/docs/v5/reference/users#follow-channel
|
||||||
|
void getChannel(QString userId,
|
||||||
|
ResultCallback<KrakenChannel> resultCallback,
|
||||||
|
KrakenFailureCallback failureCallback);
|
||||||
|
|
||||||
|
// https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id
|
||||||
|
void getUser(QString userId, ResultCallback<KrakenUser> resultCallback,
|
||||||
|
KrakenFailureCallback failureCallback);
|
||||||
|
|
||||||
|
void update(QString clientId, QString oauthToken);
|
||||||
|
|
||||||
|
static void initialize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NetworkRequest makeRequest(QString url, QUrlQuery urlQuery);
|
||||||
|
|
||||||
|
QString clientId;
|
||||||
|
QString oauthToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
Kraken *getKraken();
|
||||||
|
|
||||||
|
} // namespace chatterino
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# Twitch API
|
||||||
|
this folder describes what sort of API requests we do, what permissions are required for the requests etc
|
||||||
|
|
||||||
|
## Kraken (V5)
|
||||||
|
We use a bunch of Kraken (V5) in Chatterino2.
|
||||||
|
|
||||||
|
### Get User
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-by-id
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We implement this in `providers/twitch/api/Kraken.cpp getUser`
|
||||||
|
Used in:
|
||||||
|
* `UserInfoPopup` to get the "created at" date of a user
|
||||||
|
|
||||||
|
### Get Channel
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/channels#get-channel
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We implement this in `providers/twitch/api/Kraken.cpp getChannel`
|
||||||
|
Used in:
|
||||||
|
* `TwitchChannel::refreshTitle` to check the current stream title/game of offline channels
|
||||||
|
|
||||||
|
### Follow Channel
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#follow-channel
|
||||||
|
Requires `user_follows_edit` scope
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We implement this API in `providers/twitch/TwitchAccount.cpp followUser`
|
||||||
|
|
||||||
|
### Unfollow Channel
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#unfollow-channel
|
||||||
|
Requires `user_follows_edit` scope
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We implement this API in `providers/twitch/TwitchAccount.cpp unfollowUser`
|
||||||
|
|
||||||
|
|
||||||
|
### Get Cheermotes
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/bits#get-cheermotes
|
||||||
|
|
||||||
|
Migration path: **Not checked**
|
||||||
|
|
||||||
|
* We implement this API in `providers/twitch/TwitchChannel.cpp` to resolve a chats available cheer emotes. This helps us parse incoming messages like `pajaCheer1000`
|
||||||
|
|
||||||
|
### Get User Block List
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-block-list
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp loadIgnores`
|
||||||
|
|
||||||
|
### Block User
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#block-user
|
||||||
|
Requires `user_blocks_edit` scope
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp ignoreByID`
|
||||||
|
|
||||||
|
### Unblock User
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#unblock-user
|
||||||
|
Requires `user_blocks_edit` scope
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp unignoreByID`
|
||||||
|
|
||||||
|
### Get User Emotes
|
||||||
|
URL: https://dev.twitch.tv/docs/v5/reference/users#get-user-emotes
|
||||||
|
Requires `user_subscriptions` scope
|
||||||
|
|
||||||
|
Migration path: **Unknown**
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp loadEmotes` to figure out which emotes a user is allowed to use!
|
||||||
|
|
||||||
|
### AUTOMOD APPROVE
|
||||||
|
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp autoModAllow` to approve an automod deny/allow question
|
||||||
|
|
||||||
|
### AUTOMOD DENY
|
||||||
|
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||||
|
|
||||||
|
* We use this in `providers/twitch/TwitchAccount.cpp autoModDeny` to deny an automod deny/allow question
|
||||||
|
|
||||||
|
## Helix
|
||||||
|
Full Helix API reference: https://dev.twitch.tv/docs/api/reference
|
||||||
|
|
||||||
|
### Get Users
|
||||||
|
URL: https://dev.twitch.tv/docs/api/reference#get-users
|
||||||
|
|
||||||
|
* We implement this in `providers/twitch/api/Helix.cpp fetchUsers`.
|
||||||
|
Used in:
|
||||||
|
* `UserInfoPopup` to get ID and viewcount of username we clicked
|
||||||
|
* `CommandController` to power any commands that need to get a user ID
|
||||||
|
* `Toasts` to get the profile picture of a streamer who just went live
|
||||||
|
* `TwitchAccount` ignore and unignore features to translate user name to user ID
|
||||||
|
|
||||||
|
### Get Users Follows
|
||||||
|
URL: https://dev.twitch.tv/docs/api/reference#get-users-follows
|
||||||
|
|
||||||
|
* We implement this in `providers/twitch/api/Helix.cpp fetchUsersFollows`
|
||||||
|
Used in:
|
||||||
|
* `UserInfoPopup` to get number of followers a user has
|
||||||
|
|
||||||
|
### Get Streams
|
||||||
|
URL: https://dev.twitch.tv/docs/api/reference#get-streams
|
||||||
|
|
||||||
|
* We implement this in `providers/twitch/api/Helix.cpp fetchStreams`
|
||||||
|
Used in:
|
||||||
|
* `TwitchChannel` to get live status, game, title, and viewer count of a channel
|
||||||
|
* `NotificationController` to provide notifications for channels you might not have open in Chatterino, but are still interested in getting notifications for
|
||||||
|
|
||||||
|
## TMI
|
||||||
|
The TMI api is undocumented.
|
||||||
|
|
||||||
|
### Get Chatters
|
||||||
|
**Undocumented**
|
||||||
|
|
||||||
|
* We use this in `widgets/splits/Split.cpp showViewerList`
|
||||||
|
* We use this in `providers/twitch/TwitchChannel.cpp refreshChatters`
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "common/Singleton.hpp"
|
#include "common/Singleton.hpp"
|
||||||
|
|
||||||
#define GIF_FRAME_LENGTH 33
|
|
||||||
|
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/emoji/Emojis.hpp"
|
#include "providers/emoji/Emojis.hpp"
|
||||||
#include "providers/ffz/FfzEmotes.hpp"
|
#include "providers/ffz/FfzEmotes.hpp"
|
||||||
|
|||||||
@@ -187,8 +187,6 @@ void NativeMessagingServer::ReceiverThread::handleMessage(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << root;
|
|
||||||
|
|
||||||
if (action == "select")
|
if (action == "select")
|
||||||
{
|
{
|
||||||
QString _type = root.value("type").toString();
|
QString _type = root.value("type").toString();
|
||||||
|
|||||||
@@ -1,19 +1,120 @@
|
|||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "controllers/highlights/HighlightBlacklistUser.hpp"
|
||||||
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
|
#include "controllers/ignores/IgnorePhrase.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
#include "util/PersistSignalVector.hpp"
|
||||||
#include "util/WindowsHelper.hpp"
|
#include "util/WindowsHelper.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
ConcurrentSettings *concurrentInstance_{};
|
||||||
|
|
||||||
|
ConcurrentSettings::ConcurrentSettings()
|
||||||
|
// NOTE: these do not get deleted
|
||||||
|
: highlightedMessages(*new SignalVector<HighlightPhrase>())
|
||||||
|
, highlightedUsers(*new SignalVector<HighlightPhrase>())
|
||||||
|
, blacklistedUsers(*new SignalVector<HighlightBlacklistUser>())
|
||||||
|
, ignoredMessages(*new SignalVector<IgnorePhrase>())
|
||||||
|
, mutedChannels(*new SignalVector<QString>())
|
||||||
|
, moderationActions(*new SignalVector<ModerationAction>)
|
||||||
|
{
|
||||||
|
persist(this->highlightedMessages, "/highlighting/highlights");
|
||||||
|
persist(this->blacklistedUsers, "/highlighting/blacklist");
|
||||||
|
persist(this->highlightedUsers, "/highlighting/users");
|
||||||
|
persist(this->ignoredMessages, "/ignore/phrases");
|
||||||
|
persist(this->mutedChannels, "/pings/muted");
|
||||||
|
// tagged users?
|
||||||
|
persist(this->moderationActions, "/moderation/actions");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConcurrentSettings::isHighlightedUser(const QString &username)
|
||||||
|
{
|
||||||
|
for (const auto &highlightedUser : this->highlightedUsers)
|
||||||
|
{
|
||||||
|
if (highlightedUser.isMatch(username))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConcurrentSettings::isBlacklistedUser(const QString &username)
|
||||||
|
{
|
||||||
|
auto items = this->blacklistedUsers.readOnly();
|
||||||
|
|
||||||
|
for (const auto &blacklistedUser : *items)
|
||||||
|
{
|
||||||
|
if (blacklistedUser.isMatch(username))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConcurrentSettings::isMutedChannel(const QString &channelName)
|
||||||
|
{
|
||||||
|
for (const auto &channel : this->mutedChannels)
|
||||||
|
{
|
||||||
|
if (channelName.toLower() == channel.toLower())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConcurrentSettings::mute(const QString &channelName)
|
||||||
|
{
|
||||||
|
mutedChannels.append(channelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConcurrentSettings::unmute(const QString &channelName)
|
||||||
|
{
|
||||||
|
for (std::vector<int>::size_type i = 0; i != mutedChannels.raw().size();
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
if (mutedChannels.raw()[i].toLower() == channelName.toLower())
|
||||||
|
{
|
||||||
|
mutedChannels.removeAt(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConcurrentSettings::toggleMutedChannel(const QString &channelName)
|
||||||
|
{
|
||||||
|
if (this->isMutedChannel(channelName))
|
||||||
|
{
|
||||||
|
unmute(channelName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mute(channelName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentSettings &getCSettings()
|
||||||
|
{
|
||||||
|
// `concurrentInstance_` gets assigned in Settings ctor.
|
||||||
|
assert(concurrentInstance_);
|
||||||
|
|
||||||
|
return *concurrentInstance_;
|
||||||
|
}
|
||||||
|
|
||||||
Settings *Settings::instance_ = nullptr;
|
Settings *Settings::instance_ = nullptr;
|
||||||
|
|
||||||
Settings::Settings(const QString &settingsDirectory)
|
Settings::Settings(const QString &settingsDirectory)
|
||||||
: ABSettings(settingsDirectory)
|
: ABSettings(settingsDirectory)
|
||||||
{
|
{
|
||||||
instance_ = this;
|
instance_ = this;
|
||||||
|
concurrentInstance_ = this;
|
||||||
|
|
||||||
#ifdef USEWINSDK
|
#ifdef USEWINSDK
|
||||||
this->autorun = isRegisteredForStartup();
|
this->autorun = isRegisteredForStartup();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user