forked from OpenGamers/abaddon
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad0d4e7d4d | ||
|
|
1ca6235e09 | ||
|
|
c04fc2e60c | ||
|
|
a7bf9a2404 | ||
|
|
2065ef4940 | ||
|
|
3aefab652e |
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [Debug, RelWithDebInfo, MinSizeRel]
|
||||
buildtype: [Debug, RelWithDebInfo]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
@@ -43,7 +43,6 @@ jobs:
|
||||
del /f /s /q "${{ runner.workspace }}\build\CMakeCache.txt"
|
||||
xcopy /E /I "${{ github.workspace }}\css" "${{ runner.workspace }}\build\css"
|
||||
xcopy /E /I "${{ github.workspace }}\res" "${{ runner.workspace }}\build\res"
|
||||
xcopy /E /I "${{ github.workspace }}\fonts" "${{ runner.workspace }}\build\fonts"
|
||||
mkdir "${{ runner.workspace }}\build\share"
|
||||
xcopy /E /I "${{ github.workspace }}\ci\gtk-for-windows\gtk-nsis-pack\share\glib-2.0" "${{ runner.workspace }}\build\share\glib-2.0"
|
||||
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper.exe" "${{ runner.workspace }}\build\gspawn-win64-helper.exe"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -354,5 +354,3 @@ testdata/
|
||||
|
||||
build/
|
||||
out/
|
||||
|
||||
fonts/fonts.conf
|
||||
|
||||
@@ -2,32 +2,26 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(abaddon)
|
||||
|
||||
set(ABADDON_RESOURCE_DIR "/usr/share/abaddon" CACHE PATH "Fallback directory for resources on Linux")
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
|
||||
|
||||
set(USE_TLS TRUE)
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
find_package(CURL)
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(gtkmm REQUIRED)
|
||||
|
||||
set(USE_TLS TRUE)
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
find_package(IXWebSocket QUIET)
|
||||
if (NOT IXWebSocket_FOUND)
|
||||
message("ixwebsocket was not found and will be included as a submodule")
|
||||
find_path(IXWEBSOCKET_INCLUDE_DIRS ixwebsocket/IXWebSocket.h)
|
||||
find_library(IXWEBSOCKET_LIBRARY ixwebsocket)
|
||||
if (NOT IXWEBSOCKET_LIBRARY)
|
||||
add_subdirectory(thirdparty/IXWebSocket)
|
||||
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
|
||||
endif()
|
||||
|
||||
add_compile_definitions(SI_NO_CONVERSION) # only CSimpleIniA is used
|
||||
find_package(simpleini QUIET)
|
||||
if (NOT simpleini_FOUND)
|
||||
message("simpleini was not found and will be included as a submodule")
|
||||
include_directories(thirdparty/simpleini)
|
||||
endif()
|
||||
include_directories(thirdparty/simpleini)
|
||||
|
||||
if(MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
@@ -36,13 +30,8 @@ endif()
|
||||
if(WIN32)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
|
||||
find_package(Fontconfig REQUIRED)
|
||||
link_libraries(${Fontconfig_LIBRARIES})
|
||||
endif()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
file(GLOB ABADDON_SOURCES
|
||||
"*.h"
|
||||
"*.hpp"
|
||||
@@ -62,8 +51,6 @@ file(GLOB ABADDON_SOURCES
|
||||
)
|
||||
|
||||
add_executable(abaddon ${ABADDON_SOURCES})
|
||||
target_include_directories(abaddon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_BINARY_DIR})
|
||||
target_include_directories(abaddon PUBLIC ${GTKMM_INCLUDE_DIRS})
|
||||
target_include_directories(abaddon PUBLIC ${ZLIB_INCLUDE_DIRS})
|
||||
target_include_directories(abaddon PUBLIC ${SQLite3_INCLUDE_DIRS})
|
||||
@@ -75,8 +62,8 @@ if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
|
||||
target_link_libraries(abaddon stdc++fs)
|
||||
endif()
|
||||
|
||||
if (IXWebSocket_LIBRARIES)
|
||||
target_link_libraries(abaddon ${IXWebSocket_LIBRARIES})
|
||||
if (IXWEBSOCKET_LIBRARY)
|
||||
target_link_libraries(abaddon ${IXWEBSOCKET_LIBRARY})
|
||||
find_library(MBEDTLS_X509_LIBRARY mbedx509)
|
||||
find_library(MBEDTLS_TLS_LIBRARY mbedtls)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY mbedcrypto)
|
||||
|
||||
40
README.md
40
README.md
@@ -4,13 +4,11 @@ Alternative Discord client made in C++ with GTK
|
||||
|
||||
<img src="/.readme/s3.png">
|
||||
|
||||
[😎Discord Server](https://discord.gg/wkCU3vuzG5)
|
||||
|
||||
Current features:
|
||||
* Not Electron
|
||||
* Handles most types of chat messages including embeds, images, and replies
|
||||
* Completely styleable/customizable with CSS (if you have a system GTK theme it won't really use it though)
|
||||
* Identifies to Discord as the web client unlike other clients so less likely to be falsely flagged as spam<sup>1</sup>
|
||||
* Identifies to gateway as the web client unlike other clients so less likely to be falsely flagged as spam<sup>1</sup>
|
||||
* Set status
|
||||
* Start new DMs and group DMs
|
||||
* View user profiles (notes, mutual servers, mutual friends)
|
||||
@@ -20,14 +18,10 @@ Current features:
|
||||
* Manage emojis
|
||||
* View audit log
|
||||
* Emojis<sup>2</sup>
|
||||
* Thread support<sup>3</sup>
|
||||
* Animated avatars, server icons, emojis (can be turned off)
|
||||
|
||||
1 - Abaddon tries its best to make Discord think it's a legitimate web client. Some of the things done to do this include: using a browser user agent, sending the same IDENTIFY message that the official web client does, using API v9 endpoints in all cases, and not using endpoints the web client does not normally use. There are still a few smaller inconsistencies, however. For example the web client sends lots of telemetry via the `/science` endpoint (uBlock origin stops this) as well as in the headers of all requests. **In any case,** you should use an official client for joining servers, sending new DMs, or managing your friends list if you are afraid of being caught in Discord's spam filters (unlikely).
|
||||
|
||||
2 - Unicode emojis are subtituted manually as opposed to rendered by GTK on non-Windows platforms. This can be changed with the `stock_emojis` setting as shown at the bottom of this README. A CBDT-based font using Twemoji is provided to allow GTK to render emojis natively on Windows.
|
||||
|
||||
3 - There are some inconsistencies with thread state that might be encountered in some more uncommon cases, but they are the result of fundamental issues with Discord's thread implementation.
|
||||
1 - Other third-party clients send the IDENTIFY message that bots use which makes Discord more likely to think you are selfbotting or spamming. However, Discord still loves to ban people's accounts for no good reason, even users of the official clients. If you want to be really careful avoid joining servers really fast or cold DMing people.
|
||||
2 - Getting emojis to function properly on GTK is still something I've yet to figure out ([#5](../../issues/5)). Unicode emojis are manually searched for and replaced in several places as opposed to allowing GTK to figure it out since GTK's way of doing it doesn't work very well.
|
||||
|
||||
### Building manually (recommended if not on Windows):
|
||||
#### Windows:
|
||||
@@ -53,21 +47,12 @@ Or, do steps 1 and 2, and open CMakeLists.txt in Visual Studio if `vcpkg integra
|
||||
4. `cmake ..`
|
||||
5. `make`
|
||||
|
||||
### Downloads:
|
||||
|
||||
Latest release version: https://github.com/uowuo/abaddon/releases/latest
|
||||
|
||||
**CI:**
|
||||
|
||||
### Downloads (from CI):
|
||||
- Windows: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-windows-RelWithDebInfo.zip)
|
||||
- MacOS: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-macos-RelWithDebInfo.zip) unsigned, unpackaged, requires gtkmm3 (e.g. from homebrew)
|
||||
- Linux: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-linux-MinSizeRel.zip) unpackaged (for now), requires gtkmm3. built on Ubuntu 18.04 + gcc9
|
||||
|
||||
⚠️ If you use Windows, make sure to start from the directory containing `css` and `res`
|
||||
|
||||
On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/usr/share/abaddon`
|
||||
|
||||
`abaddon.ini` will also be automatically used if located at `~/.config/abaddon/abaddon.ini` and there is no `abaddon.ini` in the working directory
|
||||
⚠️ Make sure you start from the directory where `css` and `res` are or else stuff will be broken
|
||||
|
||||
#### Dependencies:
|
||||
* [gtkmm](https://www.gtkmm.org/en/)
|
||||
@@ -199,21 +184,10 @@ For example, memory_db would be set by adding `memory_db = true` under the line
|
||||
|
||||
#### gui
|
||||
* member_list_discriminator (true or false, default true) - show user discriminators in the member list
|
||||
* stock_emojis (true or false, default true) - allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself
|
||||
* custom_emojis (true or false, default true) - download and use custom Discord emojis
|
||||
* emojis (true or false, default true) - resolve unicode and custom emojis to images. this needs to be false to allow GTK to render emojis by itself
|
||||
* css (string) - path to the main CSS file
|
||||
* animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used
|
||||
* animated_guild_hover_only (true or false, default true) - only animate guild icons when the guild is being hovered over
|
||||
* owner_crown (true or false, default true) - show a crown next to the owner
|
||||
* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression
|
||||
* api_base (string) - override base url for Discord API
|
||||
|
||||
#### style
|
||||
#### misc
|
||||
* linkcolor (string) - color to use for links in messages
|
||||
* expandercolor (string) - color to use for the expander in the channel list
|
||||
* nsfwchannelcolor (string) - color to use for NSFW channels in the channel list
|
||||
|
||||
### Environment variables
|
||||
|
||||
* ABADDON_NO_FC (Windows only) - don't use custom font config
|
||||
* ABADDON_CONFIG - change path of configuration file to use. relative to cwd or can be absolute
|
||||
|
||||
155
abaddon.cpp
155
abaddon.cpp
@@ -2,7 +2,6 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include "platform.hpp"
|
||||
#include "discord/discord.hpp"
|
||||
#include "dialogs/token.hpp"
|
||||
#include "dialogs/editmessage.hpp"
|
||||
@@ -14,16 +13,14 @@
|
||||
#include "abaddon.hpp"
|
||||
#include "windows/guildsettingswindow.hpp"
|
||||
#include "windows/profilewindow.hpp"
|
||||
#include "windows/pinnedwindow.hpp"
|
||||
#include "windows/threadswindow.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
#endif
|
||||
|
||||
Abaddon::Abaddon()
|
||||
: m_settings(Platform::FindConfigFile())
|
||||
, m_emojis(GetResPath("/emojis.bin"))
|
||||
: m_settings("abaddon.ini")
|
||||
, m_emojis("res/emojis.bin")
|
||||
, m_discord(m_settings.GetUseMemoryDB()) { // stupid but easy
|
||||
LoadFromSettings();
|
||||
|
||||
@@ -36,11 +33,15 @@ Abaddon::Abaddon()
|
||||
m_discord.signal_message_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageDelete));
|
||||
m_discord.signal_message_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageUpdate));
|
||||
m_discord.signal_guild_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildMemberListUpdate));
|
||||
m_discord.signal_thread_member_list_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadMemberListUpdate));
|
||||
m_discord.signal_guild_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildCreate));
|
||||
m_discord.signal_guild_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildDelete));
|
||||
m_discord.signal_channel_delete().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelDelete));
|
||||
m_discord.signal_channel_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelUpdate));
|
||||
m_discord.signal_channel_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnChannelCreate));
|
||||
m_discord.signal_guild_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildUpdate));
|
||||
m_discord.signal_reaction_add().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionAdd));
|
||||
m_discord.signal_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReactionRemove));
|
||||
m_discord.signal_guild_join_request_create().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnGuildJoinRequestCreate));
|
||||
m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate));
|
||||
m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent));
|
||||
m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect));
|
||||
if (m_settings.GetPrefetch())
|
||||
@@ -67,6 +68,7 @@ Abaddon &Abaddon::Get() {
|
||||
int Abaddon::StartGTK() {
|
||||
m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon");
|
||||
|
||||
// tmp css stuff
|
||||
m_css_provider = Gtk::CssProvider::create();
|
||||
m_css_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
@@ -74,13 +76,6 @@ int Abaddon::StartGTK() {
|
||||
dlg.run();
|
||||
});
|
||||
|
||||
m_css_low_provider = Gtk::CssProvider::create();
|
||||
m_css_low_provider->signal_parsing_error().connect([this](const Glib::RefPtr<const Gtk::CssSection> §ion, const Glib::Error &error) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "low-priority css failed parsing (" + error.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
});
|
||||
|
||||
m_main_window = std::make_unique<MainWindow>();
|
||||
m_main_window->set_title(APP_TITLE);
|
||||
m_main_window->UpdateComponents();
|
||||
@@ -95,14 +90,16 @@ int Abaddon::StartGTK() {
|
||||
m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS));
|
||||
m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog));
|
||||
m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus));
|
||||
m_main_window->signal_action_reload_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadSettings));
|
||||
m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient));
|
||||
m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins));
|
||||
m_main_window->signal_action_view_threads().connect(sigc::mem_fun(*this, &Abaddon::ActionViewThreads));
|
||||
|
||||
m_main_window->signal_action_show_user_menu().connect(sigc::mem_fun(*this, &Abaddon::ShowUserMenu));
|
||||
|
||||
m_main_window->GetChannelList()->signal_action_channel_item_select().connect(sigc::mem_fun(*this, &Abaddon::ActionChannelOpened));
|
||||
m_main_window->GetChannelList()->signal_action_guild_leave().connect(sigc::mem_fun(*this, &Abaddon::ActionLeaveGuild));
|
||||
m_main_window->GetChannelList()->signal_action_guild_settings().connect(sigc::mem_fun(*this, &Abaddon::ActionGuildSettings));
|
||||
|
||||
m_main_window->GetChatWindow()->signal_action_message_delete().connect(sigc::mem_fun(*this, &Abaddon::ActionChatDeleteMessage));
|
||||
m_main_window->GetChatWindow()->signal_action_message_edit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatEditMessage));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_submit().connect(sigc::mem_fun(*this, &Abaddon::ActionChatInputSubmit));
|
||||
m_main_window->GetChatWindow()->signal_action_chat_load_history().connect(sigc::mem_fun(*this, &Abaddon::ActionChatLoadHistory));
|
||||
@@ -179,7 +176,7 @@ void Abaddon::DiscordOnReady() {
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnMessageCreate(const Message &message) {
|
||||
m_main_window->UpdateChatNewMessage(message);
|
||||
m_main_window->UpdateChatNewMessage(message.ID); // todo ill fix you later :^)
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnMessageDelete(Snowflake id, Snowflake channel_id) {
|
||||
@@ -194,8 +191,28 @@ void Abaddon::DiscordOnGuildMemberListUpdate(Snowflake guild_id) {
|
||||
m_main_window->UpdateMembers();
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnThreadMemberListUpdate(const ThreadMemberListUpdateData &data) {
|
||||
m_main_window->UpdateMembers();
|
||||
void Abaddon::DiscordOnGuildCreate(const GuildData &guild) {
|
||||
m_main_window->UpdateChannelsNewGuild(guild.ID);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnGuildDelete(Snowflake guild_id) {
|
||||
m_main_window->UpdateChannelsRemoveGuild(guild_id);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnChannelDelete(Snowflake channel_id) {
|
||||
m_main_window->UpdateChannelsRemoveChannel(channel_id);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnChannelUpdate(Snowflake channel_id) {
|
||||
m_main_window->UpdateChannelsUpdateChannel(channel_id);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnChannelCreate(Snowflake channel_id) {
|
||||
m_main_window->UpdateChannelsCreateChannel(channel_id);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnGuildUpdate(Snowflake guild_id) {
|
||||
m_main_window->UpdateChannelsUpdateGuild(guild_id);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring ¶m) {
|
||||
@@ -214,7 +231,7 @@ void Abaddon::DiscordOnGuildJoinRequestCreate(const GuildJoinRequestCreateData &
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnMessageSent(const Message &data) {
|
||||
m_main_window->UpdateChatNewMessage(data);
|
||||
m_main_window->UpdateChatNewMessage(data.ID);
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code) {
|
||||
@@ -238,15 +255,6 @@ void Abaddon::DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_c
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) {
|
||||
if (data.Thread.ID == m_main_window->GetChatActiveChannel()) {
|
||||
if (data.Thread.ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
else
|
||||
m_main_window->GetChatWindow()->SetTopic("");
|
||||
}
|
||||
}
|
||||
|
||||
const SettingsManager &Abaddon::GetSettings() const {
|
||||
return m_settings;
|
||||
}
|
||||
@@ -312,8 +320,8 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_
|
||||
void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) {
|
||||
VerificationGateDialog dlg(*m_main_window, guild_id);
|
||||
if (dlg.run() == Gtk::RESPONSE_OK) {
|
||||
const auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
const auto cb = [this](bool success) {
|
||||
if (!success) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "Failed to accept the verification gate.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
@@ -397,8 +405,8 @@ void Abaddon::on_user_menu_open_dm() {
|
||||
if (existing.has_value())
|
||||
ActionChannelOpened(*existing);
|
||||
else
|
||||
m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) {
|
||||
if (code == DiscordError::NONE) {
|
||||
m_discord.CreateDM(m_shown_user_menu_id, [this](bool success, Snowflake channel_id) {
|
||||
if (success) {
|
||||
// give the gateway a little window to send CHANNEL_CREATE
|
||||
auto cb = [this, channel_id] {
|
||||
ActionChannelOpened(channel_id);
|
||||
@@ -412,24 +420,6 @@ void Abaddon::on_user_menu_remove_recipient() {
|
||||
m_discord.RemoveGroupDMRecipient(m_main_window->GetChatActiveChannel(), m_shown_user_menu_id);
|
||||
}
|
||||
|
||||
std::string Abaddon::GetCSSPath() {
|
||||
const static auto path = Platform::FindResourceFolder() + "/css";
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetResPath() {
|
||||
const static auto path = Platform::FindResourceFolder() + "/res";
|
||||
return path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetCSSPath(const std::string &path) {
|
||||
return GetCSSPath() + path;
|
||||
}
|
||||
|
||||
std::string Abaddon::GetResPath(const std::string &path) {
|
||||
return GetResPath() + path;
|
||||
}
|
||||
|
||||
void Abaddon::ActionConnect() {
|
||||
if (!m_discord.IsStarted())
|
||||
StartDiscord();
|
||||
@@ -437,7 +427,8 @@ void Abaddon::ActionConnect() {
|
||||
}
|
||||
|
||||
void Abaddon::ActionDisconnect() {
|
||||
StopDiscord();
|
||||
if (m_discord.IsStarted())
|
||||
StopDiscord();
|
||||
}
|
||||
|
||||
void Abaddon::ActionSetToken() {
|
||||
@@ -463,10 +454,7 @@ void Abaddon::ActionJoinGuildDialog() {
|
||||
void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
if (id == m_main_window->GetChatActiveChannel()) return;
|
||||
|
||||
m_main_window->GetChatWindow()->SetTopic("");
|
||||
|
||||
const auto channel = m_discord.GetChannel(id);
|
||||
if (!channel.has_value()) return;
|
||||
if (channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::GUILD_NEWS)
|
||||
m_main_window->set_title(std::string(APP_TITLE) + " - #" + *channel->Name);
|
||||
else {
|
||||
@@ -482,7 +470,7 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
}
|
||||
m_main_window->UpdateChatActiveChannel(id);
|
||||
if (m_channels_requested.find(id) == m_channels_requested.end()) {
|
||||
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_discord.FetchMessagesInChannel(id, [this, id](const std::vector<Snowflake> &msgs) {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
m_channels_requested.insert(id);
|
||||
});
|
||||
@@ -490,14 +478,11 @@ void Abaddon::ActionChannelOpened(Snowflake id) {
|
||||
m_main_window->UpdateChatWindowContents();
|
||||
}
|
||||
|
||||
if (channel->IsThread()) {
|
||||
m_discord.SendThreadLazyLoad(id);
|
||||
if (channel->ThreadMetadata->IsArchived)
|
||||
m_main_window->GetChatWindow()->SetTopic("This thread is archived. Sending a message will unarchive it");
|
||||
} else if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM && channel->GuildID.has_value()) {
|
||||
if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) {
|
||||
m_discord.SendLazyLoad(id);
|
||||
|
||||
if (m_discord.IsVerificationRequired(*channel->GuildID))
|
||||
const auto request = m_discord.GetGuildApplication(*channel->GuildID);
|
||||
if (request.has_value() && request->ApplicationStatus == GuildApplicationStatus::STARTED)
|
||||
ShowGuildVerificationGateDialog(*channel->GuildID);
|
||||
}
|
||||
}
|
||||
@@ -510,23 +495,20 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
|
||||
return;
|
||||
|
||||
Snowflake before_id = m_main_window->GetChatOldestListedMessage();
|
||||
auto knownset = m_discord.GetMessageIDsForChannel(id);
|
||||
auto knownset = m_discord.GetMessagesForChannel(id);
|
||||
std::vector<Snowflake> knownvec(knownset.begin(), knownset.end());
|
||||
std::sort(knownvec.begin(), knownvec.end());
|
||||
auto latest = std::find_if(knownvec.begin(), knownvec.end(), [&before_id](Snowflake x) -> bool { return x == before_id; });
|
||||
int distance = std::distance(knownvec.begin(), latest);
|
||||
|
||||
if (distance >= 50) {
|
||||
std::vector<Message> msgs;
|
||||
for (auto it = knownvec.begin() + distance - 50; it != knownvec.begin() + distance; it++)
|
||||
msgs.push_back(*m_discord.GetMessage(*it));
|
||||
m_main_window->UpdateChatPrependHistory(msgs);
|
||||
m_main_window->UpdateChatPrependHistory(std::vector<Snowflake>(knownvec.begin() + distance - 50, knownvec.begin() + distance));
|
||||
return;
|
||||
}
|
||||
|
||||
m_channels_history_loading.insert(id);
|
||||
|
||||
m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector<Message> &msgs) {
|
||||
m_discord.FetchMessagesInChannelBefore(id, before_id, [this, id](const std::vector<Snowflake> &msgs) {
|
||||
m_channels_history_loading.erase(id);
|
||||
|
||||
if (msgs.size() == 0) {
|
||||
@@ -546,9 +528,12 @@ void Abaddon::ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflak
|
||||
m_discord.SendChatMessage(msg, channel);
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatDeleteMessage(Snowflake channel_id, Snowflake id) {
|
||||
m_discord.DeleteMessage(channel_id, id);
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) {
|
||||
const auto msg = m_discord.GetMessage(id);
|
||||
if (!msg.has_value()) return;
|
||||
EditMessageDialog dlg(*m_main_window);
|
||||
dlg.SetContent(msg->Content);
|
||||
auto response = dlg.run();
|
||||
@@ -632,41 +617,21 @@ void Abaddon::ActionAddRecipient(Snowflake channel_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::ActionViewPins(Snowflake channel_id) {
|
||||
const auto data = m_discord.GetChannel(channel_id);
|
||||
if (!data.has_value()) return;
|
||||
auto window = new PinnedWindow(*data);
|
||||
ManageHeapWindow(window);
|
||||
window->show();
|
||||
}
|
||||
|
||||
void Abaddon::ActionViewThreads(Snowflake channel_id) {
|
||||
auto data = m_discord.GetChannel(channel_id);
|
||||
if (!data.has_value()) return;
|
||||
if (data->IsThread()) {
|
||||
data = m_discord.GetChannel(*data->ParentID);
|
||||
if (!data.has_value()) return;
|
||||
}
|
||||
auto window = new ThreadsWindow(*data);
|
||||
ManageHeapWindow(window);
|
||||
window->show();
|
||||
}
|
||||
|
||||
bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
|
||||
ConfirmDialog dlg(window != nullptr ? *window : *m_main_window);
|
||||
dlg.SetConfirmText(prompt);
|
||||
return dlg.run() == Gtk::RESPONSE_OK;
|
||||
}
|
||||
|
||||
void Abaddon::ActionReloadSettings() {
|
||||
m_settings.Reload();
|
||||
}
|
||||
|
||||
void Abaddon::ActionReloadCSS() {
|
||||
try {
|
||||
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);
|
||||
m_css_provider->load_from_path(GetCSSPath("/" + m_settings.GetMainCSS()));
|
||||
m_css_provider->load_from_path(m_settings.GetMainCSS());
|
||||
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
|
||||
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider);
|
||||
m_css_low_provider->load_from_path(GetCSSPath("/application-low-priority.css"));
|
||||
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
|
||||
} catch (Glib::Error &e) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "css failed to load (" + e.what() + ")", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
@@ -683,8 +648,6 @@ EmojiResource &Abaddon::GetEmojis() {
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (std::getenv("ABADDON_NO_FC") == nullptr)
|
||||
Platform::SetupFonts();
|
||||
#if defined(_WIN32) && defined(_MSC_VER)
|
||||
TCHAR buf[2] { 0 };
|
||||
GetEnvironmentVariableA("GTK_CSD", buf, sizeof(buf));
|
||||
|
||||
24
abaddon.hpp
24
abaddon.hpp
@@ -36,6 +36,7 @@ public:
|
||||
void ActionChannelOpened(Snowflake id);
|
||||
void ActionChatInputSubmit(std::string msg, Snowflake channel, Snowflake referenced_message);
|
||||
void ActionChatLoadHistory(Snowflake id);
|
||||
void ActionChatDeleteMessage(Snowflake channel_id, Snowflake id);
|
||||
void ActionChatEditMessage(Snowflake channel_id, Snowflake id);
|
||||
void ActionInsertMention(Snowflake id);
|
||||
void ActionLeaveGuild(Snowflake id);
|
||||
@@ -46,11 +47,10 @@ public:
|
||||
void ActionReactionRemove(Snowflake id, const Glib::ustring ¶m);
|
||||
void ActionGuildSettings(Snowflake id);
|
||||
void ActionAddRecipient(Snowflake channel_id);
|
||||
void ActionViewPins(Snowflake channel_id);
|
||||
void ActionViewThreads(Snowflake channel_id);
|
||||
|
||||
bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr);
|
||||
|
||||
void ActionReloadSettings();
|
||||
void ActionReloadCSS();
|
||||
|
||||
ImageManager &GetImageManager();
|
||||
@@ -66,13 +66,17 @@ public:
|
||||
void DiscordOnMessageDelete(Snowflake id, Snowflake channel_id);
|
||||
void DiscordOnMessageUpdate(Snowflake id, Snowflake channel_id);
|
||||
void DiscordOnGuildMemberListUpdate(Snowflake guild_id);
|
||||
void DiscordOnThreadMemberListUpdate(const ThreadMemberListUpdateData &data);
|
||||
void DiscordOnGuildCreate(const GuildData &guild);
|
||||
void DiscordOnGuildDelete(Snowflake guild_id);
|
||||
void DiscordOnChannelDelete(Snowflake channel_id);
|
||||
void DiscordOnChannelUpdate(Snowflake channel_id);
|
||||
void DiscordOnChannelCreate(Snowflake channel_id);
|
||||
void DiscordOnGuildUpdate(Snowflake guild_id);
|
||||
void DiscordOnReactionAdd(Snowflake message_id, const Glib::ustring ¶m);
|
||||
void DiscordOnReactionRemove(Snowflake message_id, const Glib::ustring ¶m);
|
||||
void DiscordOnGuildJoinRequestCreate(const GuildJoinRequestCreateData &data);
|
||||
void DiscordOnMessageSent(const Message &data);
|
||||
void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code);
|
||||
void DiscordOnThreadUpdate(const ThreadUpdateData &data);
|
||||
|
||||
const SettingsManager &GetSettings() const;
|
||||
|
||||
@@ -80,18 +84,13 @@ public:
|
||||
|
||||
void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id);
|
||||
|
||||
void ManageHeapWindow(Gtk::Window *window);
|
||||
|
||||
static std::string GetCSSPath();
|
||||
static std::string GetResPath();
|
||||
static std::string GetCSSPath(const std::string &path);
|
||||
static std::string GetResPath(const std::string &path);
|
||||
|
||||
protected:
|
||||
void ShowGuildVerificationGateDialog(Snowflake guild_id);
|
||||
|
||||
void SetupUserMenu();
|
||||
|
||||
void ManageHeapWindow(Gtk::Window *window);
|
||||
|
||||
Snowflake m_shown_user_menu_id;
|
||||
Snowflake m_shown_user_menu_guild_id;
|
||||
|
||||
@@ -129,6 +128,5 @@ private:
|
||||
mutable std::mutex m_mutex;
|
||||
Glib::RefPtr<Gtk::Application> m_gtk_app;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_provider;
|
||||
Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ set(HARFBUZZ_INCLUDE_DIRS ${HARFBUZZ_INCLUDE_DIR};${HARFBUZZ_CONFIG_INCLUDE_DIRS
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(HarfBuzz
|
||||
FOUND_VAR HARFBUZZ_FOUND
|
||||
REQUIRED_VARS
|
||||
HARFBUZZ_LIBRARY
|
||||
HARFBUZZ_INCLUDE_DIR
|
||||
|
||||
@@ -31,6 +31,7 @@ set(ATK_INCLUDE_DIRS ${ATK_INCLUDE_DIR};${ATK_CONFIG_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(atk
|
||||
FOUND_VAR ATK_FOUND
|
||||
REQUIRED_VARS
|
||||
ATK_LIBRARY
|
||||
ATK_INCLUDE_DIR
|
||||
|
||||
@@ -42,6 +42,7 @@ set(ATKMM_INCLUDE_DIRS ${ATKMM_INCLUDE_DIR};${ATKMM_CONFIG_INCLUDE_DIR};${ATK_IN
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(atkmm
|
||||
FOUND_VAR ATKMM_FOUND
|
||||
REQUIRED_VARS
|
||||
ATKMM_LIBRARY
|
||||
ATKMM_INCLUDE_DIRS
|
||||
|
||||
@@ -31,6 +31,7 @@ set(CAIRO_INCLUDE_DIRS ${CAIRO_INCLUDE_DIR};${CAIRO_CONFIG_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(cairo
|
||||
FOUND_VAR CAIRO_FOUND
|
||||
REQUIRED_VARS
|
||||
CAIRO_LIBRARY
|
||||
CAIRO_INCLUDE_DIR
|
||||
|
||||
@@ -45,6 +45,7 @@ set(CAIROMM_INCLUDE_DIRS ${CAIROMM_INCLUDE_DIR};${CAIROMM_CONFIG_INCLUDE_DIRS};$
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(cairomm
|
||||
FOUND_VAR CAIROMM_FOUND
|
||||
REQUIRED_VARS
|
||||
CAIROMM_LIBRARY
|
||||
CAIROMM_INCLUDE_DIR
|
||||
|
||||
@@ -40,6 +40,7 @@ set(GDKMM_INCLUDE_DIRS ${GDKMM_INCLUDE_DIR};${GDKMM_CONFIG_INCLUDE_DIRS};${GDKMM
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdkmm
|
||||
FOUND_VAR GDKMM_FOUND
|
||||
REQUIRED_VARS
|
||||
GDKMM_LIBRARY
|
||||
GDKMM_INCLUDE_DIRS
|
||||
|
||||
@@ -33,6 +33,7 @@ set(GDKPIXBUF_INCLUDE_DIRS ${GDKPIXBUF_INCLUDE_DIR};${GDKPIXBUF_CONFIG_INCLUDE_D
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdkpixbuf
|
||||
FOUND_VAR GDKPIXBUF_FOUND
|
||||
REQUIRED_VARS
|
||||
GDKPIXBUF_LIBRARY
|
||||
GDKPIXBUF_INCLUDE_DIR
|
||||
|
||||
@@ -50,6 +50,7 @@ endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(glib
|
||||
FOUND_VAR GLIB_FOUND
|
||||
REQUIRED_VARS
|
||||
GLIB_LIBRARIES
|
||||
GLIB_INCLUDE_DIRS
|
||||
|
||||
@@ -60,6 +60,7 @@ set(GLIBMM_INCLUDE_DIRS ${GLIBMM_INCLUDE_DIR};${GLIBMM_CONFIG_INCLUDE_DIR};${GIO
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(glibmm
|
||||
FOUND_VAR GLIBMM_FOUND
|
||||
REQUIRED_VARS
|
||||
GLIBMM_LIBRARY
|
||||
GLIBMM_INCLUDE_DIR
|
||||
|
||||
@@ -47,6 +47,7 @@ set(GTK_INCLUDE_DIRS ${GTK_INCLUDE_DIR};${GDK_CONFIG_INCLUDE_DIR};${GDKPIXBUF_IN
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gtk
|
||||
FOUND_VAR GTK_FOUND
|
||||
REQUIRED_VARS
|
||||
GTK_LIBRARY
|
||||
GTK_INCLUDE_DIR
|
||||
|
||||
@@ -51,6 +51,7 @@ set(GTKMM_INCLUDE_DIRS ${GTKMM_INCLUDE_DIR};${GTKMM_CONFIG_INCLUDE_DIR};${GDKMM
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gtkmm
|
||||
FOUND_VAR GTKMM_FOUND
|
||||
REQUIRED_VARS
|
||||
GTKMM_LIB
|
||||
GTKMM_INCLUDE_DIRS
|
||||
|
||||
@@ -1,30 +1,17 @@
|
||||
set(IXWebSocket_LIBRARY_NAME ixwebsocket)
|
||||
find_path(IXWEBSOCKET_INCLUDE_DIR
|
||||
NAMES ixwebsocket/IXWebSocket.h)
|
||||
|
||||
find_path(IXWebSocket_INCLUDE_DIR
|
||||
NAMES ixwebsocket/IXWebSocket.h
|
||||
HINTS /usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${IXWebSocket_LIBRARY_NAME})
|
||||
find_library(IXWEBSOCKET_LIBRARY
|
||||
NAMES ixwebsocket
|
||||
HINTS ${IXWEBSOCKET_LIBRARY_ROOT})
|
||||
|
||||
|
||||
find_library(IXWebSocket_LIBRARY
|
||||
NAMES ${IXWebSocket_LIBRARY_NAME}
|
||||
PATH_SUFFIXES ${IXWebSocket_LIBRARY_NAME}
|
||||
${IXWebSocket_LIBRARY_NAME}/include)
|
||||
set(IXWEBSOCKET_LIBRARIES ${IXWEBSOCKET_LIBRARY})
|
||||
set(IXWEBSOCKET_INCLUDE_DIRS ${IXWEBSOCKET_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(IXWebSocket
|
||||
find_package_handle_standard_args(ixwebsocket
|
||||
FOUND_VAR IXWEBSOCKET_FOUND
|
||||
REQUIRED_VARS
|
||||
IXWebSocket_LIBRARY
|
||||
IXWebSocket_INCLUDE_DIR)
|
||||
|
||||
|
||||
mark_as_advanced(IXWebSocket_LIBRARY IXWebSocket_INCLUDE_DIR)
|
||||
|
||||
if (IXWebSocket_FOUND)
|
||||
find_package(OpenSSL QUIET)
|
||||
set(IXWebSocket_INCLUDE_DIRS "${IXWebSocket_INCLUDE_DIR};${OPENSSL_INCLUDE_DIR}")
|
||||
set(IXWebSocket_LIBRARIES "${IXWebSocket_LIBRARY};${OPENSSL_LIBRARIES}")
|
||||
endif()
|
||||
IXWEBSOCKET_LIBRARY
|
||||
IXWEBSOCKET_INCLUDE_DIR
|
||||
VERSION_VAR IXWEBSOCKET_VERSION)
|
||||
|
||||
@@ -22,6 +22,7 @@ set(NLOHMANN_JSON_LIBRARIES "")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(nlohmann_json
|
||||
FOUND_VAR NLOHMANN_JSON_FOUND
|
||||
REQUIRED_VARS
|
||||
NLOHMANN_JSON_INCLUDE_DIR
|
||||
VERSION_VAR NLOHMANN_JSON_VERSION)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
set(PANGO_LIBRARY_NAME pango-1.0)
|
||||
set(PANGOCAIRO_LIBRARY_NAME pangocairo-1.0)
|
||||
set(PANGOFT2_LIBRARY_NAME pangoft2-1.0)
|
||||
|
||||
find_package(HarfBuzz)
|
||||
find_package(cairo)
|
||||
@@ -44,31 +42,12 @@ find_library(PANGO_LIBRARY
|
||||
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
|
||||
${PANGO_LIBRARY_NAME}/include)
|
||||
|
||||
find_library(PANGOCAIRO_LIBRARY
|
||||
NAMES ${PANGOCAIRO_LIBRARY_NAME}
|
||||
pangocairo
|
||||
HINTS ${PANGO_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
|
||||
${PANGO_LIBRARY_NAME}/include)
|
||||
|
||||
find_library(PANGOFT2_LIBRARY
|
||||
NAMES ${PANGOFT2_LIBRARY_NAME}
|
||||
pangoft2
|
||||
HINTS ${PANGO_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${PANGO_LIBRARY_NAME}
|
||||
${PANGO_LIBRARY_NAME}/include)
|
||||
|
||||
set(PANGO_LIBRARIES ${PANGO_LIBRARY};${HARFBUZZ_LIBRARIES};${CAIRO_LIBRARIES};${FREETYPE_LIBRARIES};${PANGOCAIRO_LIBRARY};${PANGOFT2_LIBRARY})
|
||||
set(PANGO_LIBRARIES ${PANGO_LIBRARY};${HARFBUZZ_LIBRARIES};${CAIRO_LIBRARIES};${FREETYPE_LIBRARIES})
|
||||
set(PANGO_INCLUDE_DIRS ${PANGO_INCLUDE_DIR};${PANGO_CONFIG_INCLUDE_DIRS};${HARFBUZZ_INCLUDE_DIR};${CAIRO_INCLUDE_DIRS};${FREETYPE_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(pango
|
||||
FOUND_VAR PANGO_FOUND
|
||||
REQUIRED_VARS
|
||||
PANGO_LIBRARY
|
||||
PANGO_INCLUDE_DIR
|
||||
|
||||
@@ -56,6 +56,7 @@ set(PANGOMM_INCLUDE_DIRS ${PANGOMM_INCLUDE_DIR};${PANGOMM_CONFIG_INCLUDE_DIR};${
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(pangomm
|
||||
FOUND_VAR PANGOMM_FOUND
|
||||
REQUIRED_VARS
|
||||
PANGOMM_LIBRARY
|
||||
PANGOMM_INCLUDE_DIRS
|
||||
|
||||
@@ -32,6 +32,7 @@ set(SIGC++_INCLUDE_DIRS ${SIGC++_INCLUDE_DIR};${SIGC++_CONFIG_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(sigc++
|
||||
FOUND_VAR SIGC++_FOUND
|
||||
REQUIRED_VARS
|
||||
SIGC++_INCLUDE_DIR
|
||||
SIGC++_LIBRARY
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
set(simpleini_LIBRARY_NAME simpleini)
|
||||
|
||||
find_path(simpleini_INCLUDE_DIR
|
||||
NAMES SimpleIni.h
|
||||
HINTS /usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${simpleini_LIBRARY_NAME})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(simpleini
|
||||
REQUIRED_VARS
|
||||
simpleini_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(simpleini_INCLUDE_DIR)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,226 +8,172 @@
|
||||
#include <sigc++/sigc++.h>
|
||||
#include "../discord/discord.hpp"
|
||||
|
||||
constexpr static int GuildIconSize = 24;
|
||||
constexpr static int DMIconSize = 20;
|
||||
constexpr static int OrphanChannelSortOffset = -100; // forces orphan channels to the top of the list
|
||||
static const constexpr int ChannelEmojiSize = 16;
|
||||
|
||||
enum class RenderType : uint8_t {
|
||||
Guild,
|
||||
Category,
|
||||
TextChannel,
|
||||
Thread,
|
||||
class ChannelListRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
bool IsUserCollapsed;
|
||||
Snowflake ID;
|
||||
std::unordered_set<ChannelListRow *> Children;
|
||||
ChannelListRow *Parent = nullptr;
|
||||
|
||||
DMHeader,
|
||||
DM,
|
||||
virtual void Collapse();
|
||||
virtual void Expand();
|
||||
|
||||
static void MakeReadOnly(Gtk::TextView *tv);
|
||||
};
|
||||
|
||||
class CellRendererChannels : public Gtk::CellRenderer {
|
||||
class ChannelListRowDMHeader : public ChannelListRow {
|
||||
public:
|
||||
CellRendererChannels();
|
||||
virtual ~CellRendererChannels();
|
||||
|
||||
Glib::PropertyProxy<RenderType> property_type();
|
||||
Glib::PropertyProxy<Glib::ustring> property_name();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_icon();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_icon_animation();
|
||||
Glib::PropertyProxy<bool> property_expanded();
|
||||
Glib::PropertyProxy<bool> property_nsfw();
|
||||
ChannelListRowDMHeader();
|
||||
|
||||
protected:
|
||||
void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
|
||||
void get_preferred_width_for_height_vfunc(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const override;
|
||||
void get_preferred_height_vfunc(Gtk::Widget &widget, int &minimum_height, int &natural_height) const override;
|
||||
void get_preferred_height_for_width_vfunc(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const override;
|
||||
void render_vfunc(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags) override;
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
};
|
||||
|
||||
// guild functions
|
||||
void get_preferred_width_vfunc_guild(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_guild(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_guild(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_guild(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_guild(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
class StatusIndicator;
|
||||
class ChannelListRowDMChannel : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowDMChannel(const ChannelData *data);
|
||||
|
||||
// category
|
||||
void get_preferred_width_vfunc_category(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_category(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_category(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_category(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_category(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
StatusIndicator *m_status = nullptr;
|
||||
Gtk::Widget *m_lbl;
|
||||
Gtk::Image *m_icon = nullptr;
|
||||
|
||||
// text channel
|
||||
void get_preferred_width_vfunc_channel(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_channel(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_channel(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_channel(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_channel(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_close; // leave if group
|
||||
Gtk::MenuItem *m_menu_copy_id;
|
||||
};
|
||||
|
||||
// thread
|
||||
void get_preferred_width_vfunc_thread(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_thread(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_thread(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_thread(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_thread(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
class ChannelListRowGuild : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowGuild(const GuildData *data);
|
||||
|
||||
// dm header
|
||||
void get_preferred_width_vfunc_dmheader(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dmheader(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dmheader(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dmheader(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dmheader(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
int GuildIndex;
|
||||
|
||||
// dm
|
||||
void get_preferred_width_vfunc_dm(Gtk::Widget &widget, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_width_for_height_vfunc_dm(Gtk::Widget &widget, int height, int &minimum_width, int &natural_width) const;
|
||||
void get_preferred_height_vfunc_dm(Gtk::Widget &widget, int &minimum_height, int &natural_height) const;
|
||||
void get_preferred_height_for_width_vfunc_dm(Gtk::Widget &widget, int width, int &minimum_height, int &natural_height) const;
|
||||
void render_vfunc_dm(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
Gtk::Widget &widget,
|
||||
const Gdk::Rectangle &background_area,
|
||||
const Gdk::Rectangle &cell_area,
|
||||
Gtk::CellRendererState flags);
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Widget *m_lbl;
|
||||
Gtk::Image *m_icon;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_copyid;
|
||||
Gtk::MenuItem *m_menu_leave;
|
||||
Gtk::MenuItem *m_menu_settings;
|
||||
|
||||
private:
|
||||
Gtk::CellRendererText m_renderer_text;
|
||||
typedef sigc::signal<void> type_signal_copy_id;
|
||||
typedef sigc::signal<void> type_signal_leave;
|
||||
typedef sigc::signal<void> type_signal_settings;
|
||||
|
||||
Glib::Property<RenderType> m_property_type; // all
|
||||
Glib::Property<Glib::ustring> m_property_name; // all
|
||||
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf; // guild, dm
|
||||
Glib::Property<Glib::RefPtr<Gdk::PixbufAnimation>> m_property_pixbuf_animation; // guild
|
||||
Glib::Property<bool> m_property_expanded; // category
|
||||
Glib::Property<bool> m_property_nsfw; // channel
|
||||
type_signal_copy_id m_signal_copy_id;
|
||||
type_signal_leave m_signal_leave;
|
||||
type_signal_settings m_signal_settings;
|
||||
|
||||
// same pitfalls as in https://github.com/uowuo/abaddon/blob/60404783bd4ce9be26233fe66fc3a74475d9eaa3/components/cellrendererpixbufanimation.hpp#L32-L39
|
||||
// this will manifest though since guild icons can change
|
||||
// an animation or two wont be the end of the world though
|
||||
std::map<Glib::RefPtr<Gdk::PixbufAnimation>, Glib::RefPtr<Gdk::PixbufAnimationIter>> m_pixbuf_anim_iters;
|
||||
public:
|
||||
type_signal_copy_id signal_copy_id();
|
||||
type_signal_leave signal_leave();
|
||||
type_signal_settings signal_settings();
|
||||
};
|
||||
|
||||
class ChannelList : public Gtk::ScrolledWindow {
|
||||
class ChannelListRowCategory : public ChannelListRow {
|
||||
public:
|
||||
ChannelList();
|
||||
ChannelListRowCategory(const ChannelData *data);
|
||||
|
||||
void UpdateListing();
|
||||
void SetActiveChannel(Snowflake id);
|
||||
virtual void Collapse();
|
||||
virtual void Expand();
|
||||
|
||||
protected:
|
||||
void UpdateNewGuild(const GuildData &guild);
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Widget *m_lbl;
|
||||
Gtk::Arrow *m_arrow;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_copyid;
|
||||
|
||||
private:
|
||||
typedef sigc::signal<void> type_signal_copy_id;
|
||||
|
||||
type_signal_copy_id m_signal_copy_id;
|
||||
|
||||
public:
|
||||
type_signal_copy_id signal_copy_id();
|
||||
};
|
||||
|
||||
class ChannelListRowChannel : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowChannel(const ChannelData *data);
|
||||
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Widget *m_lbl;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_copyid;
|
||||
|
||||
private:
|
||||
typedef sigc::signal<void> type_signal_copy_id;
|
||||
|
||||
type_signal_copy_id m_signal_copy_id;
|
||||
|
||||
public:
|
||||
type_signal_copy_id signal_copy_id();
|
||||
};
|
||||
|
||||
class ChannelList {
|
||||
public:
|
||||
ChannelList();
|
||||
Gtk::Widget *GetRoot() const;
|
||||
void UpdateListing();
|
||||
void UpdateNewGuild(Snowflake id);
|
||||
void UpdateRemoveGuild(Snowflake id);
|
||||
void UpdateRemoveChannel(Snowflake id);
|
||||
void UpdateChannel(Snowflake id);
|
||||
void UpdateCreateChannel(const ChannelData &channel);
|
||||
void UpdateCreateDMChannel(Snowflake id);
|
||||
void UpdateCreateChannel(Snowflake id);
|
||||
void UpdateGuild(Snowflake id);
|
||||
void DeleteThreadRow(Snowflake id);
|
||||
|
||||
void OnThreadJoined(Snowflake id);
|
||||
void OnThreadRemoved(Snowflake id);
|
||||
void OnThreadDelete(const ThreadDeleteData &data);
|
||||
void OnThreadUpdate(const ThreadUpdateData &data);
|
||||
void OnThreadListSync(const ThreadListSyncData &data);
|
||||
void SetActiveChannel(Snowflake id);
|
||||
|
||||
Gtk::TreeView m_view;
|
||||
protected:
|
||||
Gtk::ListBox *m_list;
|
||||
Gtk::ScrolledWindow *m_main;
|
||||
|
||||
class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
||||
public:
|
||||
ModelColumns();
|
||||
ChannelListRowDMHeader *m_dm_header_row = nullptr;
|
||||
|
||||
Gtk::TreeModelColumn<RenderType> m_type;
|
||||
Gtk::TreeModelColumn<uint64_t> m_id;
|
||||
Gtk::TreeModelColumn<Glib::ustring> m_name;
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_icon;
|
||||
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::PixbufAnimation>> m_icon_anim;
|
||||
Gtk::TreeModelColumn<int64_t> m_sort;
|
||||
Gtk::TreeModelColumn<bool> m_nsfw;
|
||||
// Gtk::CellRenderer's property_is_expanded only works how i want it to if it has children
|
||||
// because otherwise it doesnt count as an "expander" (property_is_expander)
|
||||
// so this solution will have to do which i hate but the alternative is adding invisible children
|
||||
// to all categories without children and having a filter model but that sounds worse
|
||||
// of course its a lot better than the absolute travesty i had before
|
||||
Gtk::TreeModelColumn<bool> m_expanded;
|
||||
};
|
||||
void CollapseRow(ChannelListRow *row);
|
||||
void ExpandRow(ChannelListRow *row);
|
||||
void DeleteRow(ChannelListRow *row);
|
||||
|
||||
ModelColumns m_columns;
|
||||
Glib::RefPtr<Gtk::TreeStore> m_model;
|
||||
void UpdateChannelCategory(Snowflake id);
|
||||
|
||||
Gtk::TreeModel::iterator AddGuild(const GuildData &guild);
|
||||
Gtk::TreeModel::iterator UpdateCreateChannelCategory(const ChannelData &channel);
|
||||
Gtk::TreeModel::iterator CreateThreadRow(const Gtk::TreeNodeChildren &children, const ChannelData &channel);
|
||||
void on_row_activated(Gtk::ListBoxRow *row);
|
||||
|
||||
void UpdateChannelCategory(const ChannelData &channel);
|
||||
int m_guild_count;
|
||||
void OnMenuCopyID(Snowflake id);
|
||||
void OnGuildMenuLeave(Snowflake id);
|
||||
void OnGuildMenuSettings(Snowflake id);
|
||||
|
||||
// separation necessary because a channel and guild can share the same id
|
||||
Gtk::TreeModel::iterator GetIteratorForGuildFromID(Snowflake id);
|
||||
Gtk::TreeModel::iterator GetIteratorForChannelFromID(Snowflake id);
|
||||
Gtk::Menu m_channel_menu;
|
||||
Gtk::MenuItem *m_channel_menu_copyid;
|
||||
|
||||
bool IsTextChannel(ChannelType type);
|
||||
// i would use one map but in really old guilds there can be a channel w/ same id as the guild so this hacky shit has to do
|
||||
std::unordered_map<Snowflake, ChannelListRow *> m_guild_id_to_row;
|
||||
std::unordered_map<Snowflake, ChannelListRow *> m_id_to_row;
|
||||
|
||||
void OnRowCollapsed(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
|
||||
void OnRowExpanded(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
|
||||
bool SelectionFunc(const Glib::RefPtr<Gtk::TreeModel> &model, const Gtk::TreeModel::Path &path, bool is_currently_selected);
|
||||
bool OnButtonPressEvent(GdkEventButton *ev);
|
||||
|
||||
void MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::iterator &new_parent);
|
||||
|
||||
Gtk::TreeModel::Path m_last_selected;
|
||||
Gtk::TreeModel::Path m_dm_header;
|
||||
void InsertGuildAt(Snowflake id, int pos);
|
||||
|
||||
void AddPrivateChannels();
|
||||
void UpdateCreateDMChannel(const ChannelData &channel);
|
||||
|
||||
void OnMessageCreate(const Message &msg);
|
||||
Gtk::TreeModel::Path m_path_for_menu;
|
||||
|
||||
// cant be recovered through selection
|
||||
Gtk::TreeModel::iterator m_temporary_thread_row;
|
||||
|
||||
Gtk::Menu m_menu_guild;
|
||||
Gtk::MenuItem m_menu_guild_copy_id;
|
||||
Gtk::MenuItem m_menu_guild_settings;
|
||||
Gtk::MenuItem m_menu_guild_leave;
|
||||
|
||||
Gtk::Menu m_menu_category;
|
||||
Gtk::MenuItem m_menu_category_copy_id;
|
||||
|
||||
Gtk::Menu m_menu_channel;
|
||||
Gtk::MenuItem m_menu_channel_copy_id;
|
||||
|
||||
Gtk::Menu m_menu_dm;
|
||||
Gtk::MenuItem m_menu_dm_copy_id;
|
||||
Gtk::MenuItem m_menu_dm_close;
|
||||
|
||||
Gtk::Menu m_menu_thread;
|
||||
Gtk::MenuItem m_menu_thread_copy_id;
|
||||
Gtk::MenuItem m_menu_thread_leave;
|
||||
Gtk::MenuItem m_menu_thread_archive;
|
||||
Gtk::MenuItem m_menu_thread_unarchive;
|
||||
|
||||
void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y);
|
||||
|
||||
bool m_updating_listing = false;
|
||||
void CheckBumpDM(Snowflake channel_id);
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;
|
||||
|
||||
@@ -21,9 +21,8 @@ ChatInputIndicator::ChatInputIndicator()
|
||||
m_label.show();
|
||||
|
||||
// try loading gif
|
||||
const static auto path = Abaddon::GetResPath("/typing_indicator.gif");
|
||||
if (!std::filesystem::exists(path)) return;
|
||||
auto gif_data = ReadWholeFile(path);
|
||||
if (!std::filesystem::exists("./res/typing_indicator.gif")) return;
|
||||
auto gif_data = ReadWholeFile("./res/typing_indicator.gif");
|
||||
auto loader = Gdk::PixbufLoader::create();
|
||||
loader->signal_size_prepared().connect([&](int inw, int inh) {
|
||||
int w, h;
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
#include "chatmessage.hpp"
|
||||
#include "chatlist.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "constants.hpp"
|
||||
|
||||
ChatList::ChatList() {
|
||||
m_list.get_style_context()->add_class("messages");
|
||||
|
||||
set_can_focus(false);
|
||||
set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
|
||||
signal_edge_reached().connect(sigc::mem_fun(*this, &ChatList::OnScrollEdgeOvershot));
|
||||
|
||||
auto v = get_vadjustment();
|
||||
v->signal_value_changed().connect([this, v] {
|
||||
m_should_scroll_to_bottom = v->get_upper() - v->get_page_size() <= v->get_value();
|
||||
});
|
||||
|
||||
m_list.signal_size_allocate().connect([this](Gtk::Allocation &) {
|
||||
if (m_should_scroll_to_bottom)
|
||||
ScrollToBottom();
|
||||
});
|
||||
|
||||
m_list.set_focus_hadjustment(get_hadjustment());
|
||||
m_list.set_focus_vadjustment(get_vadjustment());
|
||||
m_list.set_selection_mode(Gtk::SELECTION_NONE);
|
||||
m_list.set_hexpand(true);
|
||||
m_list.set_vexpand(true);
|
||||
|
||||
add(m_list);
|
||||
|
||||
m_list.show();
|
||||
|
||||
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_menu_copy_id->signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(m_menu_selected_message));
|
||||
});
|
||||
m_menu_copy_id->show();
|
||||
m_menu.append(*m_menu_copy_id);
|
||||
|
||||
m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("Delete Message"));
|
||||
m_menu_delete_message->signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().DeleteMessage(m_active_channel, m_menu_selected_message);
|
||||
});
|
||||
m_menu_delete_message->show();
|
||||
m_menu.append(*m_menu_delete_message);
|
||||
|
||||
m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("Edit Message"));
|
||||
m_menu_edit_message->signal_activate().connect([this] {
|
||||
m_signal_action_message_edit.emit(m_active_channel, m_menu_selected_message);
|
||||
});
|
||||
m_menu_edit_message->show();
|
||||
m_menu.append(*m_menu_edit_message);
|
||||
|
||||
m_menu_copy_content = Gtk::manage(new Gtk::MenuItem("Copy Content"));
|
||||
m_menu_copy_content->signal_activate().connect([this] {
|
||||
const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(m_menu_selected_message);
|
||||
if (msg.has_value())
|
||||
Gtk::Clipboard::get()->set_text(msg->Content);
|
||||
});
|
||||
m_menu_copy_content->show();
|
||||
m_menu.append(*m_menu_copy_content);
|
||||
|
||||
m_menu_reply_to = Gtk::manage(new Gtk::MenuItem("Reply To"));
|
||||
m_menu_reply_to->signal_activate().connect([this] {
|
||||
m_signal_action_reply_to.emit(m_menu_selected_message);
|
||||
});
|
||||
m_menu_reply_to->show();
|
||||
m_menu.append(*m_menu_reply_to);
|
||||
|
||||
m_menu_unpin = Gtk::manage(new Gtk::MenuItem("Unpin"));
|
||||
m_menu_unpin->signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().Unpin(m_active_channel, m_menu_selected_message, [](...) {});
|
||||
});
|
||||
m_menu.append(*m_menu_unpin);
|
||||
|
||||
m_menu_pin = Gtk::manage(new Gtk::MenuItem("Pin"));
|
||||
m_menu_pin->signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().Pin(m_active_channel, m_menu_selected_message, [](...) {});
|
||||
});
|
||||
m_menu.append(*m_menu_pin);
|
||||
|
||||
m_menu.show();
|
||||
}
|
||||
|
||||
void ChatList::Clear() {
|
||||
auto children = m_list.get_children();
|
||||
auto it = children.begin();
|
||||
while (it != children.end()) {
|
||||
delete *it;
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatList::SetActiveChannel(Snowflake id) {
|
||||
m_active_channel = id;
|
||||
}
|
||||
|
||||
void ChatList::ProcessNewMessage(const Message &data, bool prepend) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (!discord.IsStarted()) return;
|
||||
|
||||
// delete preview message when gateway sends it back
|
||||
if (!data.IsPending && data.Nonce.has_value() && data.Author.ID == discord.GetUserData().ID) {
|
||||
for (auto [id, widget] : m_id_to_widget) {
|
||||
if (dynamic_cast<ChatMessageItemContainer *>(widget)->Nonce == *data.Nonce) {
|
||||
RemoveMessageAndHeader(widget);
|
||||
m_id_to_widget.erase(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageHeader *last_row = nullptr;
|
||||
bool should_attach = false;
|
||||
if (!m_separate_all && m_num_rows > 0) {
|
||||
if (prepend)
|
||||
last_row = dynamic_cast<ChatMessageHeader *>(m_list.get_row_at_index(0));
|
||||
else
|
||||
last_row = dynamic_cast<ChatMessageHeader *>(m_list.get_row_at_index(m_num_rows - 1));
|
||||
|
||||
if (last_row != nullptr) {
|
||||
const uint64_t diff = std::max(data.ID, last_row->NewestID) - std::min(data.ID, last_row->NewestID);
|
||||
if (last_row->UserID == data.Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval)))
|
||||
should_attach = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_num_messages++;
|
||||
|
||||
if (m_should_scroll_to_bottom && !prepend) {
|
||||
while (m_num_messages > MaxMessagesForChatCull) {
|
||||
auto first_it = m_id_to_widget.begin();
|
||||
RemoveMessageAndHeader(first_it->second);
|
||||
m_id_to_widget.erase(first_it);
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageHeader *header;
|
||||
if (should_attach) {
|
||||
header = last_row;
|
||||
} else {
|
||||
const auto chan = discord.GetChannel(m_active_channel);
|
||||
Snowflake guild_id;
|
||||
if (chan.has_value() && chan->GuildID.has_value())
|
||||
guild_id = *chan->GuildID;
|
||||
const auto user_id = data.Author.ID;
|
||||
const auto user = discord.GetUser(user_id);
|
||||
if (!user.has_value()) return;
|
||||
|
||||
header = Gtk::manage(new ChatMessageHeader(data));
|
||||
header->signal_action_insert_mention().connect([this, user_id]() {
|
||||
m_signal_action_insert_mention.emit(user_id);
|
||||
});
|
||||
|
||||
header->signal_action_open_user_menu().connect([this, user_id, guild_id](const GdkEvent *event) {
|
||||
m_signal_action_open_user_menu.emit(event, user_id, guild_id);
|
||||
});
|
||||
|
||||
m_num_rows++;
|
||||
}
|
||||
|
||||
auto *content = ChatMessageItemContainer::FromMessage(data);
|
||||
if (content != nullptr) {
|
||||
header->AddContent(content, prepend);
|
||||
m_id_to_widget[data.ID] = content;
|
||||
|
||||
const auto cb = [this, id = data.ID](GdkEventButton *ev) -> bool {
|
||||
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
|
||||
m_menu_selected_message = id;
|
||||
|
||||
const auto &client = Abaddon::Get().GetDiscordClient();
|
||||
const auto data = client.GetMessage(id);
|
||||
if (!data.has_value()) return false;
|
||||
const auto channel = client.GetChannel(m_active_channel);
|
||||
|
||||
bool has_manage = channel.has_value() && (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM);
|
||||
if (!has_manage)
|
||||
has_manage = client.HasChannelPermission(client.GetUserData().ID, m_active_channel, Permission::MANAGE_MESSAGES);
|
||||
|
||||
m_menu_edit_message->set_visible(!m_use_pinned_menu);
|
||||
m_menu_reply_to->set_visible(!m_use_pinned_menu);
|
||||
m_menu_unpin->set_visible(has_manage && data->IsPinned);
|
||||
m_menu_pin->set_visible(has_manage && !data->IsPinned);
|
||||
|
||||
if (data->IsDeleted()) {
|
||||
m_menu_delete_message->set_sensitive(false);
|
||||
m_menu_edit_message->set_sensitive(false);
|
||||
} else {
|
||||
const bool can_edit = client.GetUserData().ID == data->Author.ID;
|
||||
const bool can_delete = can_edit || has_manage;
|
||||
m_menu_delete_message->set_sensitive(can_delete);
|
||||
m_menu_edit_message->set_sensitive(can_edit);
|
||||
}
|
||||
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
content->signal_button_press_event().connect(cb);
|
||||
|
||||
if (!data.IsPending) {
|
||||
content->signal_action_reaction_add().connect([this, id = data.ID](const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_add.emit(id, param);
|
||||
});
|
||||
content->signal_action_reaction_remove().connect([this, id = data.ID](const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_remove.emit(id, param);
|
||||
});
|
||||
content->signal_action_channel_click().connect([this](const Snowflake &id) {
|
||||
m_signal_action_channel_click.emit(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
header->set_margin_left(5);
|
||||
header->show_all();
|
||||
|
||||
if (!should_attach) {
|
||||
if (prepend)
|
||||
m_list.prepend(*header);
|
||||
else
|
||||
m_list.add(*header);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatList::DeleteMessage(Snowflake id) {
|
||||
auto widget = m_id_to_widget.find(id);
|
||||
if (widget == m_id_to_widget.end()) return;
|
||||
|
||||
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
|
||||
if (x != nullptr)
|
||||
x->UpdateAttributes();
|
||||
}
|
||||
|
||||
void ChatList::RefetchMessage(Snowflake id) {
|
||||
auto widget = m_id_to_widget.find(id);
|
||||
if (widget == m_id_to_widget.end()) return;
|
||||
|
||||
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
|
||||
if (x != nullptr) {
|
||||
x->UpdateContent();
|
||||
x->UpdateAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
Snowflake ChatList::GetOldestListedMessage() {
|
||||
if (m_id_to_widget.size() > 0)
|
||||
return m_id_to_widget.begin()->first;
|
||||
else
|
||||
return Snowflake::Invalid;
|
||||
}
|
||||
|
||||
void ChatList::UpdateMessageReactions(Snowflake id) {
|
||||
auto it = m_id_to_widget.find(id);
|
||||
if (it == m_id_to_widget.end()) return;
|
||||
auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
|
||||
if (widget == nullptr) return;
|
||||
widget->UpdateReactions();
|
||||
}
|
||||
|
||||
void ChatList::SetFailedByNonce(const std::string &nonce) {
|
||||
for (auto [id, widget] : m_id_to_widget) {
|
||||
if (auto *container = dynamic_cast<ChatMessageItemContainer *>(widget); container->Nonce == nonce) {
|
||||
container->SetFailed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Snowflake> ChatList::GetRecentAuthors() {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
std::vector<Snowflake> ret;
|
||||
|
||||
std::map<Snowflake, Gtk::Widget *> ordered(m_id_to_widget.begin(), m_id_to_widget.end());
|
||||
|
||||
for (auto it = ordered.crbegin(); it != ordered.crend(); it++) {
|
||||
const auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
|
||||
if (widget == nullptr) continue;
|
||||
const auto msg = discord.GetMessage(widget->ID);
|
||||
if (!msg.has_value()) continue;
|
||||
if (std::find(ret.begin(), ret.end(), msg->Author.ID) == ret.end())
|
||||
ret.push_back(msg->Author.ID);
|
||||
}
|
||||
|
||||
const auto chan = discord.GetChannel(m_active_channel);
|
||||
if (chan->GuildID.has_value()) {
|
||||
const auto others = discord.GetUsersInGuild(*chan->GuildID);
|
||||
for (const auto id : others)
|
||||
if (std::find(ret.begin(), ret.end(), id) == ret.end())
|
||||
ret.push_back(id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ChatList::SetSeparateAll(bool separate) {
|
||||
m_separate_all = true;
|
||||
}
|
||||
|
||||
void ChatList::SetUsePinnedMenu() {
|
||||
m_use_pinned_menu = true;
|
||||
}
|
||||
|
||||
void ChatList::ActuallyRemoveMessage(Snowflake id) {
|
||||
auto it = m_id_to_widget.find(id);
|
||||
if (it != m_id_to_widget.end())
|
||||
RemoveMessageAndHeader(it->second);
|
||||
}
|
||||
|
||||
void ChatList::OnScrollEdgeOvershot(Gtk::PositionType pos) {
|
||||
if (pos == Gtk::POS_TOP)
|
||||
m_signal_action_chat_load_history.emit(m_active_channel);
|
||||
}
|
||||
|
||||
void ChatList::ScrollToBottom() {
|
||||
auto x = get_vadjustment();
|
||||
x->set_value(x->get_upper());
|
||||
}
|
||||
|
||||
void ChatList::RemoveMessageAndHeader(Gtk::Widget *widget) {
|
||||
auto *header = dynamic_cast<ChatMessageHeader *>(widget->get_ancestor(Gtk::ListBoxRow::get_type()));
|
||||
if (header != nullptr) {
|
||||
if (header->GetChildContent().size() == 1) {
|
||||
m_num_rows--;
|
||||
delete header;
|
||||
} else {
|
||||
delete widget;
|
||||
}
|
||||
} else {
|
||||
delete widget;
|
||||
}
|
||||
m_num_messages--;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_message_edit ChatList::signal_action_message_edit() {
|
||||
return m_signal_action_message_edit;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_chat_submit ChatList::signal_action_chat_submit() {
|
||||
return m_signal_action_chat_submit;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_chat_load_history ChatList::signal_action_chat_load_history() {
|
||||
return m_signal_action_chat_load_history;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_channel_click ChatList::signal_action_channel_click() {
|
||||
return m_signal_action_channel_click;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_insert_mention ChatList::signal_action_insert_mention() {
|
||||
return m_signal_action_insert_mention;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_open_user_menu ChatList::signal_action_open_user_menu() {
|
||||
return m_signal_action_open_user_menu;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_reaction_add ChatList::signal_action_reaction_add() {
|
||||
return m_signal_action_reaction_add;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_reaction_remove ChatList::signal_action_reaction_remove() {
|
||||
return m_signal_action_reaction_remove;
|
||||
}
|
||||
|
||||
ChatList::type_signal_action_reply_to ChatList::signal_action_reply_to() {
|
||||
return m_signal_action_reply_to;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include "discord/snowflake.hpp"
|
||||
|
||||
class ChatList : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ChatList();
|
||||
void Clear();
|
||||
void SetActiveChannel(Snowflake id);
|
||||
template<typename Iter>
|
||||
void SetMessages(Iter begin, Iter end);
|
||||
template<typename Iter>
|
||||
void PrependMessages(Iter begin, Iter end);
|
||||
void ProcessNewMessage(const Message &data, bool prepend);
|
||||
void DeleteMessage(Snowflake id);
|
||||
void RefetchMessage(Snowflake id);
|
||||
Snowflake GetOldestListedMessage();
|
||||
void UpdateMessageReactions(Snowflake id);
|
||||
void SetFailedByNonce(const std::string &nonce);
|
||||
std::vector<Snowflake> GetRecentAuthors();
|
||||
void SetSeparateAll(bool separate);
|
||||
void SetUsePinnedMenu(); // i think i need a better way to do menus
|
||||
void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name
|
||||
|
||||
private:
|
||||
void OnScrollEdgeOvershot(Gtk::PositionType pos);
|
||||
void ScrollToBottom();
|
||||
void RemoveMessageAndHeader(Gtk::Widget *widget);
|
||||
|
||||
bool m_use_pinned_menu = false;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_copy_id;
|
||||
Gtk::MenuItem *m_menu_copy_content;
|
||||
Gtk::MenuItem *m_menu_delete_message;
|
||||
Gtk::MenuItem *m_menu_edit_message;
|
||||
Gtk::MenuItem *m_menu_reply_to;
|
||||
Gtk::MenuItem *m_menu_unpin;
|
||||
Gtk::MenuItem *m_menu_pin;
|
||||
Snowflake m_menu_selected_message;
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
int m_num_messages = 0;
|
||||
int m_num_rows = 0;
|
||||
std::map<Snowflake, Gtk::Widget *> m_id_to_widget;
|
||||
|
||||
bool m_should_scroll_to_bottom = true;
|
||||
Gtk::ListBox m_list;
|
||||
|
||||
bool m_separate_all = false;
|
||||
|
||||
public:
|
||||
// these are all forwarded by the parent
|
||||
using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
|
||||
using type_signal_action_chat_submit = sigc::signal<void, std::string, Snowflake, Snowflake>;
|
||||
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_channel_click = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_insert_mention = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_open_user_menu = sigc::signal<void, const GdkEvent *, Snowflake, Snowflake>;
|
||||
using type_signal_action_reaction_add = sigc::signal<void, Snowflake, Glib::ustring>;
|
||||
using type_signal_action_reaction_remove = sigc::signal<void, Snowflake, Glib::ustring>;
|
||||
using type_signal_action_reply_to = sigc::signal<void, Snowflake>;
|
||||
|
||||
type_signal_action_message_edit signal_action_message_edit();
|
||||
type_signal_action_chat_submit signal_action_chat_submit();
|
||||
type_signal_action_chat_load_history signal_action_chat_load_history();
|
||||
type_signal_action_channel_click signal_action_channel_click();
|
||||
type_signal_action_insert_mention signal_action_insert_mention();
|
||||
type_signal_action_open_user_menu signal_action_open_user_menu();
|
||||
type_signal_action_reaction_add signal_action_reaction_add();
|
||||
type_signal_action_reaction_remove signal_action_reaction_remove();
|
||||
type_signal_action_reply_to signal_action_reply_to();
|
||||
|
||||
private:
|
||||
type_signal_action_message_edit m_signal_action_message_edit;
|
||||
type_signal_action_chat_submit m_signal_action_chat_submit;
|
||||
type_signal_action_chat_load_history m_signal_action_chat_load_history;
|
||||
type_signal_action_channel_click m_signal_action_channel_click;
|
||||
type_signal_action_insert_mention m_signal_action_insert_mention;
|
||||
type_signal_action_open_user_menu m_signal_action_open_user_menu;
|
||||
type_signal_action_reaction_add m_signal_action_reaction_add;
|
||||
type_signal_action_reaction_remove m_signal_action_reaction_remove;
|
||||
type_signal_action_reply_to m_signal_action_reply_to;
|
||||
};
|
||||
|
||||
template<typename Iter>
|
||||
inline void ChatList::SetMessages(Iter begin, Iter end) {
|
||||
Clear();
|
||||
m_num_rows = 0;
|
||||
m_num_messages = 0;
|
||||
m_id_to_widget.clear();
|
||||
|
||||
for (Iter it = begin; it != end; it++)
|
||||
ProcessNewMessage(*it, false);
|
||||
|
||||
ScrollToBottom();
|
||||
}
|
||||
|
||||
template<typename Iter>
|
||||
inline void ChatList::PrependMessages(Iter begin, Iter end) {
|
||||
const auto old_upper = get_vadjustment()->get_upper();
|
||||
const auto old_value = get_vadjustment()->get_value();
|
||||
for (Iter it = begin; it != end; it++)
|
||||
ProcessNewMessage(*it, true);
|
||||
// force everything to process before getting new values
|
||||
while (Gtk::Main::events_pending())
|
||||
Gtk::Main::iteration();
|
||||
const auto new_upper = get_vadjustment()->get_upper();
|
||||
if (old_value == 0.0 && (new_upper - old_upper) > 0.0)
|
||||
get_vadjustment()->set_value(new_upper - old_upper);
|
||||
// this isn't ideal
|
||||
}
|
||||
@@ -9,11 +9,32 @@ constexpr static int AvatarSize = 32;
|
||||
constexpr static int EmbedImageWidth = 400;
|
||||
constexpr static int EmbedImageHeight = 300;
|
||||
constexpr static int ThumbnailSize = 100;
|
||||
constexpr static int StickerComponentSize = 160;
|
||||
|
||||
ChatMessageItemContainer::ChatMessageItemContainer()
|
||||
: m_main(Gtk::ORIENTATION_VERTICAL) {
|
||||
add(m_main);
|
||||
ChatMessageItemContainer::ChatMessageItemContainer() {
|
||||
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
add(*m_main);
|
||||
|
||||
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_copy_id));
|
||||
m_menu.append(*m_menu_copy_id);
|
||||
|
||||
m_menu_delete_message = Gtk::manage(new Gtk::MenuItem("Delete Message"));
|
||||
m_menu_delete_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_delete_message));
|
||||
m_menu.append(*m_menu_delete_message);
|
||||
|
||||
m_menu_edit_message = Gtk::manage(new Gtk::MenuItem("Edit Message"));
|
||||
m_menu_edit_message->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_edit_message));
|
||||
m_menu.append(*m_menu_edit_message);
|
||||
|
||||
m_menu_copy_content = Gtk::manage(new Gtk::MenuItem("Copy Content"));
|
||||
m_menu_copy_content->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_copy_content));
|
||||
m_menu.append(*m_menu_copy_content);
|
||||
|
||||
m_menu_reply_to = Gtk::manage(new Gtk::MenuItem("Reply To"));
|
||||
m_menu_reply_to->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_menu_reply_to));
|
||||
m_menu.append(*m_menu_reply_to);
|
||||
|
||||
m_menu.show_all();
|
||||
|
||||
m_link_menu_copy = Gtk::manage(new Gtk::MenuItem("Copy Link"));
|
||||
m_link_menu_copy->signal_activate().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::on_link_menu_copy));
|
||||
@@ -22,74 +43,68 @@ ChatMessageItemContainer::ChatMessageItemContainer()
|
||||
m_link_menu.show_all();
|
||||
}
|
||||
|
||||
ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &data) {
|
||||
ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(Snowflake id) {
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetMessage(id);
|
||||
if (!data.has_value()) return nullptr;
|
||||
|
||||
auto *container = Gtk::manage(new ChatMessageItemContainer);
|
||||
container->ID = data.ID;
|
||||
container->ChannelID = data.ChannelID;
|
||||
container->ID = data->ID;
|
||||
container->ChannelID = data->ChannelID;
|
||||
|
||||
if (data.Nonce.has_value())
|
||||
container->Nonce = *data.Nonce;
|
||||
if (data->Nonce.has_value())
|
||||
container->Nonce = *data->Nonce;
|
||||
|
||||
if (data.Content.size() > 0 || data.Type != MessageType::DEFAULT) {
|
||||
container->m_text_component = container->CreateTextComponent(data);
|
||||
if (data->Content.size() > 0 || data->Type != MessageType::DEFAULT) {
|
||||
container->m_text_component = container->CreateTextComponent(&*data);
|
||||
container->AttachEventHandlers(*container->m_text_component);
|
||||
container->m_main.add(*container->m_text_component);
|
||||
container->m_main->add(*container->m_text_component);
|
||||
}
|
||||
|
||||
if ((data.MessageReference.has_value() || data.Interaction.has_value()) && data.Type != MessageType::CHANNEL_FOLLOW_ADD) {
|
||||
auto *widget = container->CreateReplyComponent(data);
|
||||
if (widget != nullptr) {
|
||||
container->m_main.add(*widget);
|
||||
container->m_main.child_property_position(*widget) = 0; // eek
|
||||
}
|
||||
if ((data->MessageReference.has_value() || data->Interaction.has_value()) && data->Type != MessageType::CHANNEL_FOLLOW_ADD) {
|
||||
auto *widget = container->CreateReplyComponent(*data);
|
||||
container->m_main->add(*widget);
|
||||
container->m_main->child_property_position(*widget) = 0; // eek
|
||||
}
|
||||
|
||||
// there should only ever be 1 embed (i think?)
|
||||
if (data.Embeds.size() == 1) {
|
||||
const auto &embed = data.Embeds[0];
|
||||
if (data->Embeds.size() == 1) {
|
||||
const auto &embed = data->Embeds[0];
|
||||
if (IsEmbedImageOnly(embed)) {
|
||||
auto *widget = container->CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height);
|
||||
container->AttachEventHandlers(*widget);
|
||||
container->m_main.add(*widget);
|
||||
container->m_main->add(*widget);
|
||||
} else {
|
||||
container->m_embed_component = container->CreateEmbedComponent(embed);
|
||||
container->AttachEventHandlers(*container->m_embed_component);
|
||||
container->m_main.add(*container->m_embed_component);
|
||||
container->m_main->add(*container->m_embed_component);
|
||||
}
|
||||
}
|
||||
|
||||
// i dont think attachments can be edited
|
||||
// also this can definitely be done much better holy shit
|
||||
for (const auto &a : data.Attachments) {
|
||||
for (const auto &a : data->Attachments) {
|
||||
if (IsURLViewableImage(a.ProxyURL) && a.Width.has_value() && a.Height.has_value()) {
|
||||
auto *widget = container->CreateImageComponent(a.ProxyURL, a.URL, *a.Width, *a.Height);
|
||||
container->m_main.add(*widget);
|
||||
container->m_main->add(*widget);
|
||||
} else {
|
||||
auto *widget = container->CreateAttachmentComponent(a);
|
||||
container->m_main.add(*widget);
|
||||
container->m_main->add(*widget);
|
||||
}
|
||||
}
|
||||
|
||||
// only 1?
|
||||
/*
|
||||
DEPRECATED
|
||||
if (data.Stickers.has_value()) {
|
||||
const auto &sticker = data.Stickers.value()[0];
|
||||
if (data->Stickers.has_value()) {
|
||||
const auto &sticker = data->Stickers.value()[0];
|
||||
// todo: lottie, proper apng
|
||||
if (sticker.FormatType == StickerFormatType::PNG || sticker.FormatType == StickerFormatType::APNG) {
|
||||
auto *widget = container->CreateStickerComponent(sticker);
|
||||
container->m_main->add(*widget);
|
||||
}
|
||||
}*/
|
||||
|
||||
if (data.StickerItems.has_value()) {
|
||||
auto *widget = container->CreateStickersComponent(*data.StickerItems);
|
||||
container->m_main.add(*widget);
|
||||
}
|
||||
|
||||
if (data.Reactions.has_value() && data.Reactions->size() > 0) {
|
||||
container->m_reactions_component = container->CreateReactionsComponent(data);
|
||||
container->m_main.add(*container->m_reactions_component);
|
||||
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
|
||||
container->m_reactions_component = container->CreateReactionsComponent(*data);
|
||||
container->m_main->add(*container->m_reactions_component);
|
||||
}
|
||||
|
||||
container->UpdateAttributes();
|
||||
@@ -111,7 +126,7 @@ void ChatMessageItemContainer::UpdateContent() {
|
||||
if (data->Embeds.size() == 1) {
|
||||
m_embed_component = CreateEmbedComponent(data->Embeds[0]);
|
||||
AttachEventHandlers(*m_embed_component);
|
||||
m_main.add(*m_embed_component);
|
||||
m_main->add(*m_embed_component);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,15 +138,13 @@ void ChatMessageItemContainer::UpdateReactions() {
|
||||
if (data->Reactions.has_value() && data->Reactions->size() > 0) {
|
||||
m_reactions_component = CreateReactionsComponent(*data);
|
||||
m_reactions_component->show_all();
|
||||
m_main.add(*m_reactions_component);
|
||||
m_main->add(*m_reactions_component);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::SetFailed() {
|
||||
if (m_text_component != nullptr) {
|
||||
m_text_component->get_style_context()->remove_class("pending");
|
||||
m_text_component->get_style_context()->add_class("failed");
|
||||
}
|
||||
m_text_component->get_style_context()->remove_class("pending");
|
||||
m_text_component->get_style_context()->add_class("failed");
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::UpdateAttributes() {
|
||||
@@ -147,7 +160,7 @@ void ChatMessageItemContainer::UpdateAttributes() {
|
||||
m_attrib_label = Gtk::manage(new Gtk::Label);
|
||||
m_attrib_label->set_halign(Gtk::ALIGN_START);
|
||||
m_attrib_label->show();
|
||||
m_main.add(*m_attrib_label); // todo: maybe insert markup into existing text widget's buffer if the circumstances are right (or pack horizontally)
|
||||
m_main->add(*m_attrib_label); // todo: maybe insert markup into existing text widget's buffer if the circumstances are right (or pack horizontally)
|
||||
}
|
||||
|
||||
if (deleted)
|
||||
@@ -168,10 +181,10 @@ void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, std::string
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message &data) {
|
||||
Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message *data) {
|
||||
auto *tv = Gtk::manage(new Gtk::TextView);
|
||||
|
||||
if (data.IsPending)
|
||||
if (data->IsPending)
|
||||
tv->get_style_context()->add_class("pending");
|
||||
tv->get_style_context()->add_class("message-text");
|
||||
tv->set_can_focus(false);
|
||||
@@ -276,20 +289,6 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
|
||||
case MessageType::GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING: {
|
||||
b->insert_markup(s, "<i><span color='#999999'>This server has failed Discovery activity requirements for 3 weeks in a row. If this server fails for 1 more week, it will be removed from Discovery.</span></i>");
|
||||
} break;
|
||||
case MessageType::THREAD_CREATED: {
|
||||
const auto author = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
|
||||
if (data->MessageReference.has_value() && data->MessageReference->ChannelID.has_value()) {
|
||||
auto iter = b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span></i>");
|
||||
auto tag = b->create_tag();
|
||||
tag->property_weight() = Pango::WEIGHT_BOLD;
|
||||
m_channel_tagmap[tag] = *data->MessageReference->ChannelID;
|
||||
b->insert_with_tag(iter, data->Content, tag);
|
||||
|
||||
tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false);
|
||||
} else {
|
||||
b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>");
|
||||
}
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -503,7 +502,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateAttachmentComponent(const Attachmen
|
||||
return ev;
|
||||
}
|
||||
|
||||
Gtk::Widget *ChatMessageItemContainer::CreateStickerComponentDeprecated(const StickerData &data) {
|
||||
Gtk::Widget *ChatMessageItemContainer::CreateStickerComponent(const StickerData &data) {
|
||||
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
auto *imgw = Gtk::manage(new Gtk::Image);
|
||||
box->add(*imgw);
|
||||
@@ -520,27 +519,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickerComponentDeprecated(const St
|
||||
return box;
|
||||
}
|
||||
|
||||
Gtk::Widget *ChatMessageItemContainer::CreateStickersComponent(const std::vector<StickerItem> &data) {
|
||||
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
|
||||
for (const auto &sticker : data) {
|
||||
// no lottie
|
||||
if (sticker.FormatType != StickerFormatType::PNG && sticker.FormatType != StickerFormatType::APNG) continue;
|
||||
auto *ev = Gtk::manage(new Gtk::EventBox);
|
||||
auto *img = Gtk::manage(new LazyImage(sticker.GetURL(), StickerComponentSize, StickerComponentSize, false));
|
||||
img->set_size_request(StickerComponentSize, StickerComponentSize); // should this go in LazyImage ?
|
||||
img->show();
|
||||
ev->show();
|
||||
ev->add(*img);
|
||||
box->add(*ev);
|
||||
}
|
||||
|
||||
box->show();
|
||||
|
||||
AttachEventHandlers(*box);
|
||||
return box;
|
||||
}
|
||||
|
||||
Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message &data) {
|
||||
auto *flow = Gtk::manage(new Gtk::FlowBox);
|
||||
flow->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
|
||||
@@ -623,8 +601,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateReactionsComponent(const Message &d
|
||||
}
|
||||
|
||||
Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) {
|
||||
if (data.Type == MessageType::THREAD_CREATED) return nullptr;
|
||||
|
||||
auto *box = Gtk::manage(new Gtk::Box);
|
||||
auto *lbl = Gtk::manage(new Gtk::Label);
|
||||
lbl->set_single_line_mode(true);
|
||||
@@ -692,10 +668,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
|
||||
// which of course would not be an issue if i could figure out how to get fonts to work on this god-forsaken framework
|
||||
// oh well
|
||||
// but ill manually get colors for the user who is being replied to
|
||||
if (referenced.GuildID.has_value())
|
||||
lbl->set_markup(get_author_markup(referenced.Author.ID, *referenced.GuildID) + ": " + text);
|
||||
else
|
||||
lbl->set_markup(get_author_markup(referenced.Author.ID) + ": " + text);
|
||||
lbl->set_markup(get_author_markup(referenced.Author.ID, *referenced.GuildID) + ": " + text);
|
||||
}
|
||||
} else {
|
||||
lbl->set_markup("<i>reply unavailable</i>");
|
||||
@@ -835,11 +808,11 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleEmojis(Gtk::TextView &tv) {
|
||||
static const bool stock_emojis = Abaddon::Get().GetSettings().GetShowStockEmojis();
|
||||
static const bool custom_emojis = Abaddon::Get().GetSettings().GetShowCustomEmojis();
|
||||
|
||||
if (stock_emojis) HandleStockEmojis(tv);
|
||||
if (custom_emojis) HandleCustomEmojis(tv);
|
||||
static bool emojis = Abaddon::Get().GetSettings().GetShowEmojis();
|
||||
if (emojis) {
|
||||
HandleStockEmojis(tv);
|
||||
HandleCustomEmojis(tv);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::CleanupEmojis(Glib::RefPtr<Gtk::TextBuffer> buf) {
|
||||
@@ -893,10 +866,8 @@ void ChatMessageItemContainer::HandleChannelMentions(Glib::RefPtr<Gtk::TextBuffe
|
||||
}
|
||||
|
||||
auto tag = buf->create_tag();
|
||||
if (chan->Type == ChannelType::GUILD_TEXT) {
|
||||
m_channel_tagmap[tag] = channel_id;
|
||||
tag->property_weight() = Pango::WEIGHT_BOLD;
|
||||
}
|
||||
m_channel_tagmap[tag] = channel_id;
|
||||
tag->property_weight() = Pango::WEIGHT_BOLD;
|
||||
|
||||
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
|
||||
const auto chars_end = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mend);
|
||||
@@ -1018,6 +989,52 @@ bool ChatMessageItemContainer::OnLinkClick(GdkEventButton *ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::ShowMenu(GdkEvent *event) {
|
||||
const auto &client = Abaddon::Get().GetDiscordClient();
|
||||
const auto data = client.GetMessage(ID);
|
||||
if (data->IsDeleted()) {
|
||||
m_menu_delete_message->set_sensitive(false);
|
||||
m_menu_edit_message->set_sensitive(false);
|
||||
} else {
|
||||
const bool can_edit = client.GetUserData().ID == data->Author.ID;
|
||||
const bool can_delete = can_edit || client.HasChannelPermission(client.GetUserData().ID, ChannelID, Permission::MANAGE_MESSAGES);
|
||||
m_menu_delete_message->set_sensitive(can_delete);
|
||||
m_menu_edit_message->set_sensitive(can_edit);
|
||||
}
|
||||
|
||||
m_menu.popup_at_pointer(event);
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_menu_copy_id() {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(ID));
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_menu_delete_message() {
|
||||
m_signal_action_delete.emit();
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_menu_edit_message() {
|
||||
m_signal_action_edit.emit();
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_menu_copy_content() {
|
||||
const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(ID);
|
||||
if (msg.has_value())
|
||||
Gtk::Clipboard::get()->set_text(msg->Content);
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::on_menu_reply_to() {
|
||||
m_signal_action_reply_to.emit(ID);
|
||||
}
|
||||
|
||||
ChatMessageItemContainer::type_signal_action_delete ChatMessageItemContainer::signal_action_delete() {
|
||||
return m_signal_action_delete;
|
||||
}
|
||||
|
||||
ChatMessageItemContainer::type_signal_action_edit ChatMessageItemContainer::signal_action_edit() {
|
||||
return m_signal_action_edit;
|
||||
}
|
||||
|
||||
ChatMessageItemContainer::type_signal_channel_click ChatMessageItemContainer::signal_action_channel_click() {
|
||||
return m_signal_action_channel_click;
|
||||
}
|
||||
@@ -1030,10 +1047,14 @@ ChatMessageItemContainer::type_signal_action_reaction_remove ChatMessageItemCont
|
||||
return m_signal_action_reaction_remove;
|
||||
}
|
||||
|
||||
ChatMessageItemContainer::type_signal_action_reply_to ChatMessageItemContainer::signal_action_reply_to() {
|
||||
return m_signal_action_reply_to;
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) {
|
||||
const auto on_button_press_event = [this](GdkEventButton *e) -> bool {
|
||||
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
|
||||
event(reinterpret_cast<GdkEvent *>(e)); // illegal ooooooh
|
||||
const auto on_button_press_event = [this](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY) {
|
||||
ShowMenu(reinterpret_cast<GdkEvent *>(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1042,22 +1063,28 @@ void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) {
|
||||
widget.signal_button_press_event().connect(on_button_press_event, false);
|
||||
}
|
||||
|
||||
ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
: m_main_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_content_box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_meta_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_avatar(Abaddon::Get().GetImageManager().GetPlaceholder(AvatarSize)) {
|
||||
UserID = data.Author.ID;
|
||||
ChannelID = data.ChannelID;
|
||||
ChatMessageHeader::ChatMessageHeader(const Message *data) {
|
||||
UserID = data->Author.ID;
|
||||
ChannelID = data->ChannelID;
|
||||
|
||||
m_main_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_content_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
m_content_box_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_meta_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_meta_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_author = Gtk::manage(new Gtk::Label);
|
||||
m_timestamp = Gtk::manage(new Gtk::Label);
|
||||
m_avatar_ev = Gtk::manage(new Gtk::EventBox);
|
||||
|
||||
const auto author = Abaddon::Get().GetDiscordClient().GetUser(UserID);
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
m_avatar = Gtk::manage(new Gtk::Image(img.GetPlaceholder(AvatarSize)));
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_static_avatar = pb->scale_simple(AvatarSize, AvatarSize, Gdk::INTERP_BILINEAR);
|
||||
m_avatar.property_pixbuf() = m_static_avatar;
|
||||
m_avatar->property_pixbuf() = m_static_avatar;
|
||||
};
|
||||
img.LoadFromURL(author->GetAvatarURL(data.GuildID), sigc::track_obj(cb, *this));
|
||||
img.LoadFromURL(author->GetAvatarURL(), sigc::track_obj(cb, *this));
|
||||
|
||||
if (author->HasAnimatedAvatar()) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
@@ -1067,23 +1094,23 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
}
|
||||
|
||||
get_style_context()->add_class("message-container");
|
||||
m_author.get_style_context()->add_class("message-container-author");
|
||||
m_timestamp.get_style_context()->add_class("message-container-timestamp");
|
||||
m_avatar.get_style_context()->add_class("message-container-avatar");
|
||||
m_author->get_style_context()->add_class("message-container-author");
|
||||
m_timestamp->get_style_context()->add_class("message-container-timestamp");
|
||||
m_avatar->get_style_context()->add_class("message-container-avatar");
|
||||
|
||||
m_avatar.set_valign(Gtk::ALIGN_START);
|
||||
m_avatar.set_margin_right(10);
|
||||
m_avatar->set_valign(Gtk::ALIGN_START);
|
||||
m_avatar->set_margin_right(10);
|
||||
|
||||
m_author.set_markup(data.Author.GetEscapedBoldName());
|
||||
m_author.set_single_line_mode(true);
|
||||
m_author.set_line_wrap(false);
|
||||
m_author.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_author.set_xalign(0.f);
|
||||
m_author.set_can_focus(false);
|
||||
m_author->set_markup(data->Author.GetEscapedBoldName());
|
||||
m_author->set_single_line_mode(true);
|
||||
m_author->set_line_wrap(false);
|
||||
m_author->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_author->set_xalign(0.f);
|
||||
m_author->set_can_focus(false);
|
||||
|
||||
m_meta_ev.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press));
|
||||
m_meta_ev->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageHeader::on_author_button_press));
|
||||
|
||||
if (author->IsBot || data.WebhookID.has_value()) {
|
||||
if (author->IsBot || data->WebhookID.has_value()) {
|
||||
m_extra = Gtk::manage(new Gtk::Label);
|
||||
m_extra->get_style_context()->add_class("message-container-extra");
|
||||
m_extra->set_single_line_mode(true);
|
||||
@@ -1093,62 +1120,62 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
}
|
||||
if (author->IsBot)
|
||||
m_extra->set_markup("<b>BOT</b>");
|
||||
else if (data.WebhookID.has_value())
|
||||
else if (data->WebhookID.has_value())
|
||||
m_extra->set_markup("<b>Webhook</b>");
|
||||
|
||||
m_timestamp.set_text(data.ID.GetLocalTimestamp());
|
||||
m_timestamp.set_hexpand(true);
|
||||
m_timestamp.set_halign(Gtk::ALIGN_END);
|
||||
m_timestamp.set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_timestamp.set_opacity(0.5);
|
||||
m_timestamp.set_single_line_mode(true);
|
||||
m_timestamp.set_margin_start(12);
|
||||
m_timestamp.set_can_focus(false);
|
||||
m_timestamp->set_text(data->ID.GetLocalTimestamp());
|
||||
m_timestamp->set_hexpand(true);
|
||||
m_timestamp->set_halign(Gtk::ALIGN_END);
|
||||
m_timestamp->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
m_timestamp->set_opacity(0.5);
|
||||
m_timestamp->set_single_line_mode(true);
|
||||
m_timestamp->set_margin_start(12);
|
||||
m_timestamp->set_can_focus(false);
|
||||
|
||||
m_main_box.set_hexpand(true);
|
||||
m_main_box.set_vexpand(true);
|
||||
m_main_box.set_can_focus(true);
|
||||
m_main_box->set_hexpand(true);
|
||||
m_main_box->set_vexpand(true);
|
||||
m_main_box->set_can_focus(true);
|
||||
|
||||
m_meta_box.set_hexpand(true);
|
||||
m_meta_box.set_can_focus(false);
|
||||
m_meta_box->set_hexpand(true);
|
||||
m_meta_box->set_can_focus(false);
|
||||
|
||||
m_content_box.set_can_focus(false);
|
||||
m_content_box->set_can_focus(false);
|
||||
|
||||
const auto on_enter_cb = [this](const GdkEventCrossing *event) -> bool {
|
||||
if (m_anim_avatar)
|
||||
m_avatar.property_pixbuf_animation() = m_anim_avatar;
|
||||
m_avatar->property_pixbuf_animation() = m_anim_avatar;
|
||||
return false;
|
||||
};
|
||||
const auto on_leave_cb = [this](const GdkEventCrossing *event) -> bool {
|
||||
if (m_anim_avatar)
|
||||
m_avatar.property_pixbuf() = m_static_avatar;
|
||||
m_avatar->property_pixbuf() = m_static_avatar;
|
||||
return false;
|
||||
};
|
||||
|
||||
m_content_box_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
m_meta_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
m_avatar_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
m_content_box_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
m_meta_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
m_avatar_ev->add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
|
||||
if (Abaddon::Get().GetSettings().GetShowAnimations()) {
|
||||
m_content_box_ev.signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_content_box_ev.signal_leave_notify_event().connect(on_leave_cb);
|
||||
m_meta_ev.signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_meta_ev.signal_leave_notify_event().connect(on_leave_cb);
|
||||
m_avatar_ev.signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_avatar_ev.signal_leave_notify_event().connect(on_leave_cb);
|
||||
m_content_box_ev->signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_content_box_ev->signal_leave_notify_event().connect(on_leave_cb);
|
||||
m_meta_ev->signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_meta_ev->signal_leave_notify_event().connect(on_leave_cb);
|
||||
m_avatar_ev->signal_enter_notify_event().connect(on_enter_cb);
|
||||
m_avatar_ev->signal_leave_notify_event().connect(on_leave_cb);
|
||||
}
|
||||
|
||||
m_meta_box.add(m_author);
|
||||
m_meta_box->add(*m_author);
|
||||
if (m_extra != nullptr)
|
||||
m_meta_box.add(*m_extra);
|
||||
m_meta_box->add(*m_extra);
|
||||
|
||||
m_meta_box.add(m_timestamp);
|
||||
m_meta_ev.add(m_meta_box);
|
||||
m_content_box.add(m_meta_ev);
|
||||
m_avatar_ev.add(m_avatar);
|
||||
m_main_box.add(m_avatar_ev);
|
||||
m_content_box_ev.add(m_content_box);
|
||||
m_main_box.add(m_content_box_ev);
|
||||
add(m_main_box);
|
||||
m_meta_box->add(*m_timestamp);
|
||||
m_meta_ev->add(*m_meta_box);
|
||||
m_content_box->add(*m_meta_ev);
|
||||
m_avatar_ev->add(*m_avatar);
|
||||
m_main_box->add(*m_avatar_ev);
|
||||
m_content_box_ev->add(*m_content_box);
|
||||
m_main_box->add(*m_content_box_ev);
|
||||
add(*m_main_box);
|
||||
|
||||
set_margin_bottom(8);
|
||||
|
||||
@@ -1160,27 +1187,25 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
|
||||
auto guild_member_update_cb = [this](const auto &, const auto &) { UpdateNameColor(); };
|
||||
discord.signal_guild_member_update().connect(sigc::track_obj(guild_member_update_cb, *this));
|
||||
UpdateNameColor();
|
||||
AttachUserMenuHandler(m_meta_ev);
|
||||
AttachUserMenuHandler(m_avatar_ev);
|
||||
AttachUserMenuHandler(*m_meta_ev);
|
||||
AttachUserMenuHandler(*m_avatar_ev);
|
||||
}
|
||||
|
||||
void ChatMessageHeader::UpdateNameColor() {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild_id = discord.GetChannel(ChannelID)->GuildID;
|
||||
const auto role_id = discord.GetMemberHoistedRole(*guild_id, UserID, true);
|
||||
const auto user = discord.GetUser(UserID);
|
||||
if (!user.has_value()) return;
|
||||
const auto chan = discord.GetChannel(ChannelID);
|
||||
bool is_guild = chan.has_value() && chan->GuildID.has_value();
|
||||
if (is_guild) {
|
||||
const auto role_id = discord.GetMemberHoistedRole(*chan->GuildID, UserID, true);
|
||||
const auto role = discord.GetRole(role_id);
|
||||
const auto role = discord.GetRole(role_id);
|
||||
|
||||
std::string md;
|
||||
if (role.has_value())
|
||||
m_author.set_markup("<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + user->GetEscapedName() + "</span>");
|
||||
else
|
||||
m_author.set_markup("<span weight='bold'>" + user->GetEscapedName() + "</span>");
|
||||
} else
|
||||
m_author.set_markup("<span weight='bold'>" + user->GetEscapedName() + "</span>");
|
||||
std::string md;
|
||||
if (role.has_value())
|
||||
md = "<span weight='bold' color='#" + IntToCSSColor(role->Color) + "'>" + user->GetEscapedName() + "</span>";
|
||||
else
|
||||
md = "<span weight='bold'>" + user->GetEscapedName() + "</span>";
|
||||
|
||||
m_author->set_markup(md);
|
||||
}
|
||||
|
||||
std::vector<Gtk::Widget *> ChatMessageHeader::GetChildContent() {
|
||||
@@ -1190,11 +1215,7 @@ std::vector<Gtk::Widget *> ChatMessageHeader::GetChildContent() {
|
||||
void ChatMessageHeader::AttachUserMenuHandler(Gtk::Widget &widget) {
|
||||
widget.signal_button_press_event().connect([this](GdkEventButton *ev) -> bool {
|
||||
if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) {
|
||||
auto info = Abaddon::Get().GetDiscordClient().GetChannel(ChannelID);
|
||||
Snowflake guild_id;
|
||||
if (info.has_value() && info->GuildID.has_value())
|
||||
guild_id = *info->GuildID;
|
||||
Abaddon::Get().ShowUserMenu(reinterpret_cast<GdkEvent *>(ev), UserID, guild_id);
|
||||
m_signal_action_open_user_menu.emit(reinterpret_cast<GdkEvent *>(ev));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1221,13 +1242,12 @@ ChatMessageHeader::type_signal_action_open_user_menu ChatMessageHeader::signal_a
|
||||
|
||||
void ChatMessageHeader::AddContent(Gtk::Widget *widget, bool prepend) {
|
||||
m_content_widgets.push_back(widget);
|
||||
const auto cb = [this, widget]() {
|
||||
widget->signal_unmap().connect([this, widget]() {
|
||||
m_content_widgets.erase(std::remove(m_content_widgets.begin(), m_content_widgets.end(), widget), m_content_widgets.end());
|
||||
};
|
||||
widget->signal_unmap().connect(sigc::track_obj(cb, *this, *widget), false);
|
||||
m_content_box.add(*widget);
|
||||
});
|
||||
m_content_box->add(*widget);
|
||||
if (prepend)
|
||||
m_content_box.reorder_child(*widget, 1);
|
||||
m_content_box->reorder_child(*widget, 1);
|
||||
if (auto *x = dynamic_cast<ChatMessageItemContainer *>(widget)) {
|
||||
if (x->ID > NewestID)
|
||||
NewestID = x->ID;
|
||||
|
||||
@@ -10,7 +10,7 @@ public:
|
||||
std::string Nonce;
|
||||
|
||||
ChatMessageItemContainer();
|
||||
static ChatMessageItemContainer *FromMessage(const Message &data);
|
||||
static ChatMessageItemContainer *FromMessage(Snowflake id);
|
||||
|
||||
// attributes = edited, deleted
|
||||
void UpdateAttributes();
|
||||
@@ -20,13 +20,12 @@ public:
|
||||
|
||||
protected:
|
||||
void AddClickHandler(Gtk::Widget *widget, std::string);
|
||||
Gtk::TextView *CreateTextComponent(const Message &data); // Message.Content
|
||||
Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
|
||||
void UpdateTextComponent(Gtk::TextView *tv);
|
||||
Gtk::Widget *CreateEmbedComponent(const EmbedData &data); // Message.Embeds[0]
|
||||
Gtk::Widget *CreateImageComponent(const std::string &proxy_url, const std::string &url, int inw, int inh);
|
||||
Gtk::Widget *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
|
||||
Gtk::Widget *CreateStickerComponentDeprecated(const StickerData &data);
|
||||
Gtk::Widget *CreateStickersComponent(const std::vector<StickerItem> &data);
|
||||
Gtk::Widget *CreateStickerComponent(const StickerData &data);
|
||||
Gtk::Widget *CreateReactionsComponent(const Message &data);
|
||||
Gtk::Widget *CreateReplyComponent(const Message &data);
|
||||
|
||||
@@ -57,9 +56,23 @@ protected:
|
||||
std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap;
|
||||
|
||||
void AttachEventHandlers(Gtk::Widget &widget);
|
||||
void ShowMenu(GdkEvent *event);
|
||||
|
||||
Gtk::EventBox *_ev;
|
||||
Gtk::Box m_main;
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_copy_id;
|
||||
Gtk::MenuItem *m_menu_copy_content;
|
||||
Gtk::MenuItem *m_menu_delete_message;
|
||||
Gtk::MenuItem *m_menu_edit_message;
|
||||
Gtk::MenuItem *m_menu_reply_to;
|
||||
|
||||
void on_menu_copy_id();
|
||||
void on_menu_delete_message();
|
||||
void on_menu_edit_message();
|
||||
void on_menu_copy_content();
|
||||
void on_menu_reply_to();
|
||||
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_main;
|
||||
Gtk::Label *m_attrib_label = nullptr;
|
||||
|
||||
Gtk::TextView *m_text_component = nullptr;
|
||||
@@ -67,18 +80,29 @@ protected:
|
||||
Gtk::Widget *m_reactions_component = nullptr;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void> type_signal_action_delete;
|
||||
typedef sigc::signal<void> type_signal_action_edit;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_click;
|
||||
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_add;
|
||||
typedef sigc::signal<void, Glib::ustring> type_signal_action_reaction_remove;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_reply_to;
|
||||
typedef sigc::signal<void> type_signal_enter;
|
||||
typedef sigc::signal<void> type_signal_leave;
|
||||
|
||||
type_signal_action_delete signal_action_delete();
|
||||
type_signal_action_edit signal_action_edit();
|
||||
type_signal_channel_click signal_action_channel_click();
|
||||
type_signal_action_reaction_add signal_action_reaction_add();
|
||||
type_signal_action_reaction_remove signal_action_reaction_remove();
|
||||
type_signal_action_reply_to signal_action_reply_to();
|
||||
|
||||
private:
|
||||
type_signal_action_delete m_signal_action_delete;
|
||||
type_signal_action_edit m_signal_action_edit;
|
||||
type_signal_channel_click m_signal_action_channel_click;
|
||||
type_signal_action_reaction_add m_signal_action_reaction_add;
|
||||
type_signal_action_reaction_remove m_signal_action_reaction_remove;
|
||||
type_signal_action_reply_to m_signal_action_reply_to;
|
||||
};
|
||||
|
||||
class ChatMessageHeader : public Gtk::ListBoxRow {
|
||||
@@ -87,28 +111,28 @@ public:
|
||||
Snowflake ChannelID;
|
||||
Snowflake NewestID = 0;
|
||||
|
||||
ChatMessageHeader(const Message &data);
|
||||
ChatMessageHeader(const Message *data);
|
||||
void AddContent(Gtk::Widget *widget, bool prepend);
|
||||
void UpdateNameColor();
|
||||
std::vector<Gtk::Widget *> GetChildContent();
|
||||
std::vector<Gtk::Widget*> GetChildContent();
|
||||
|
||||
protected:
|
||||
void AttachUserMenuHandler(Gtk::Widget &widget);
|
||||
|
||||
bool on_author_button_press(GdkEventButton *ev);
|
||||
|
||||
std::vector<Gtk::Widget *> m_content_widgets;
|
||||
std::vector<Gtk::Widget*> m_content_widgets;
|
||||
|
||||
Gtk::Box m_main_box;
|
||||
Gtk::Box m_content_box;
|
||||
Gtk::EventBox m_content_box_ev;
|
||||
Gtk::Box m_meta_box;
|
||||
Gtk::EventBox m_meta_ev;
|
||||
Gtk::Label m_author;
|
||||
Gtk::Label m_timestamp;
|
||||
Gtk::Box *m_main_box;
|
||||
Gtk::Box *m_content_box;
|
||||
Gtk::EventBox *m_content_box_ev;
|
||||
Gtk::Box *m_meta_box;
|
||||
Gtk::EventBox *m_meta_ev;
|
||||
Gtk::Label *m_author;
|
||||
Gtk::Label *m_timestamp;
|
||||
Gtk::Label *m_extra = nullptr;
|
||||
Gtk::Image m_avatar;
|
||||
Gtk::EventBox m_avatar_ev;
|
||||
Gtk::Image *m_avatar;
|
||||
Gtk::EventBox *m_avatar_ev;
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> m_static_avatar;
|
||||
Glib::RefPtr<Gdk::PixbufAnimation> m_anim_avatar;
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "ratelimitindicator.hpp"
|
||||
#include "chatinput.hpp"
|
||||
#include "chatlist.hpp"
|
||||
|
||||
constexpr static uint64_t SnowflakeSplitDifference = 600;
|
||||
|
||||
ChatWindow::ChatWindow() {
|
||||
Abaddon::Get().GetDiscordClient().signal_message_send_fail().connect(sigc::mem_fun(*this, &ChatWindow::OnMessageSendFail));
|
||||
|
||||
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
m_chat = Gtk::manage(new ChatList);
|
||||
m_list = Gtk::manage(new Gtk::ListBox);
|
||||
m_scroll = Gtk::manage(new Gtk::ScrolledWindow);
|
||||
m_input = Gtk::manage(new ChatInput);
|
||||
m_input_indicator = Gtk::manage(new ChatInputIndicator);
|
||||
m_rate_limit_indicator = Gtk::manage(new RateLimitIndicator);
|
||||
@@ -27,14 +29,33 @@ ChatWindow::ChatWindow() {
|
||||
m_input_indicator->show();
|
||||
|
||||
m_main->get_style_context()->add_class("messages");
|
||||
m_list->get_style_context()->add_class("messages");
|
||||
|
||||
m_main->set_hexpand(true);
|
||||
m_main->set_vexpand(true);
|
||||
|
||||
m_topic.get_style_context()->add_class("channel-topic");
|
||||
m_topic.add(m_topic_text);
|
||||
m_topic_text.set_halign(Gtk::ALIGN_START);
|
||||
m_topic_text.show();
|
||||
m_scroll->signal_edge_reached().connect(sigc::mem_fun(*this, &ChatWindow::OnScrollEdgeOvershot));
|
||||
|
||||
auto v = m_scroll->get_vadjustment();
|
||||
v->signal_value_changed().connect([this, v] {
|
||||
m_should_scroll_to_bottom = v->get_upper() - v->get_page_size() <= v->get_value();
|
||||
});
|
||||
|
||||
m_scroll->set_can_focus(false);
|
||||
m_scroll->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
|
||||
m_scroll->show();
|
||||
|
||||
m_list->signal_size_allocate().connect([this](Gtk::Allocation &) {
|
||||
if (m_should_scroll_to_bottom)
|
||||
ScrollToBottom();
|
||||
});
|
||||
|
||||
m_list->set_selection_mode(Gtk::SELECTION_NONE);
|
||||
m_list->set_hexpand(true);
|
||||
m_list->set_vexpand(true);
|
||||
m_list->set_focus_hadjustment(m_scroll->get_hadjustment());
|
||||
m_list->set_focus_vadjustment(m_scroll->get_vadjustment());
|
||||
m_list->show();
|
||||
|
||||
m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit));
|
||||
m_input->signal_escape().connect([this]() {
|
||||
@@ -50,47 +71,41 @@ ChatWindow::ChatWindow() {
|
||||
});
|
||||
|
||||
m_completer.SetGetRecentAuthors([this]() -> auto {
|
||||
return m_chat->GetRecentAuthors();
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
std::vector<Snowflake> ret;
|
||||
|
||||
std::map<Snowflake, Gtk::Widget *> ordered(m_id_to_widget.begin(), m_id_to_widget.end());
|
||||
|
||||
for (auto it = ordered.crbegin(); it != ordered.crend(); it++) {
|
||||
const auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
|
||||
if (widget == nullptr) continue;
|
||||
const auto msg = discord.GetMessage(widget->ID);
|
||||
if (!msg.has_value()) continue;
|
||||
if (std::find(ret.begin(), ret.end(), msg->Author.ID) == ret.end())
|
||||
ret.push_back(msg->Author.ID);
|
||||
}
|
||||
|
||||
const auto chan = discord.GetChannel(m_active_channel);
|
||||
if (chan->GuildID.has_value()) {
|
||||
const auto others = discord.GetUsersInGuild(*chan->GuildID);
|
||||
for (const auto id : others)
|
||||
if (std::find(ret.begin(), ret.end(), id) == ret.end())
|
||||
ret.push_back(id);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
m_completer.show();
|
||||
|
||||
m_chat->signal_action_channel_click().connect([this](Snowflake id) {
|
||||
m_signal_action_channel_click.emit(id);
|
||||
});
|
||||
m_chat->signal_action_chat_load_history().connect([this](Snowflake id) {
|
||||
m_signal_action_chat_load_history.emit(id);
|
||||
});
|
||||
m_chat->signal_action_chat_submit().connect([this](const std::string &str, Snowflake channel_id, Snowflake referenced_id) {
|
||||
m_signal_action_chat_submit.emit(str, channel_id, referenced_id);
|
||||
});
|
||||
m_chat->signal_action_insert_mention().connect([this](Snowflake id) {
|
||||
// lowkey gross
|
||||
m_signal_action_insert_mention.emit(id);
|
||||
});
|
||||
m_chat->signal_action_message_edit().connect([this](Snowflake channel_id, Snowflake message_id) {
|
||||
m_signal_action_message_edit.emit(channel_id, message_id);
|
||||
});
|
||||
m_chat->signal_action_reaction_add().connect([this](Snowflake id, const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_add.emit(id, param);
|
||||
});
|
||||
m_chat->signal_action_reaction_remove().connect([this](Snowflake id, const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_remove.emit(id, param);
|
||||
});
|
||||
m_chat->signal_action_reply_to().connect([this](Snowflake id) {
|
||||
StartReplying(id);
|
||||
});
|
||||
m_chat->show();
|
||||
|
||||
m_meta->set_hexpand(true);
|
||||
m_meta->set_halign(Gtk::ALIGN_FILL);
|
||||
m_meta->show();
|
||||
|
||||
m_meta->add(*m_input_indicator);
|
||||
m_meta->add(*m_rate_limit_indicator);
|
||||
//m_scroll->add(*m_list);
|
||||
m_main->add(m_topic);
|
||||
m_main->add(*m_chat);
|
||||
m_scroll->add(*m_list);
|
||||
m_main->add(*m_scroll);
|
||||
m_main->add(m_completer);
|
||||
m_main->add(*m_input);
|
||||
m_main->add(*m_meta);
|
||||
@@ -102,36 +117,63 @@ Gtk::Widget *ChatWindow::GetRoot() const {
|
||||
}
|
||||
|
||||
void ChatWindow::Clear() {
|
||||
m_chat->Clear();
|
||||
SetMessages(std::set<Snowflake>());
|
||||
}
|
||||
|
||||
void ChatWindow::SetMessages(const std::vector<Message> &msgs) {
|
||||
m_chat->SetMessages(msgs.begin(), msgs.end());
|
||||
void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) {
|
||||
// empty the listbox
|
||||
auto children = m_list->get_children();
|
||||
auto it = children.begin();
|
||||
while (it != children.end()) {
|
||||
delete *it;
|
||||
it++;
|
||||
}
|
||||
|
||||
m_num_rows = 0;
|
||||
m_num_messages = 0;
|
||||
m_id_to_widget.clear();
|
||||
|
||||
for (const auto &id : msgs) {
|
||||
ProcessNewMessage(id, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWindow::SetActiveChannel(Snowflake id) {
|
||||
m_active_channel = id;
|
||||
m_chat->SetActiveChannel(id);
|
||||
m_input_indicator->SetActiveChannel(id);
|
||||
m_rate_limit_indicator->SetActiveChannel(id);
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
}
|
||||
|
||||
void ChatWindow::AddNewMessage(const Message &data) {
|
||||
m_chat->ProcessNewMessage(data, false);
|
||||
void ChatWindow::AddNewMessage(Snowflake id) {
|
||||
ProcessNewMessage(id, false);
|
||||
}
|
||||
|
||||
void ChatWindow::DeleteMessage(Snowflake id) {
|
||||
m_chat->DeleteMessage(id);
|
||||
auto widget = m_id_to_widget.find(id);
|
||||
if (widget == m_id_to_widget.end()) return;
|
||||
|
||||
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
|
||||
if (x != nullptr)
|
||||
x->UpdateAttributes();
|
||||
}
|
||||
|
||||
void ChatWindow::UpdateMessage(Snowflake id) {
|
||||
m_chat->RefetchMessage(id);
|
||||
auto widget = m_id_to_widget.find(id);
|
||||
if (widget == m_id_to_widget.end()) return;
|
||||
|
||||
auto *x = dynamic_cast<ChatMessageItemContainer *>(widget->second);
|
||||
if (x != nullptr) {
|
||||
x->UpdateContent();
|
||||
x->UpdateAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWindow::AddNewHistory(const std::vector<Message> &msgs) {
|
||||
m_chat->PrependMessages(msgs.crbegin(), msgs.crend());
|
||||
void ChatWindow::AddNewHistory(const std::vector<Snowflake> &id) {
|
||||
std::set<Snowflake> ids(id.begin(), id.end());
|
||||
for (auto it = ids.rbegin(); it != ids.rend(); it++)
|
||||
ProcessNewMessage(*it, true);
|
||||
}
|
||||
|
||||
void ChatWindow::InsertChatInput(std::string text) {
|
||||
@@ -139,16 +181,15 @@ void ChatWindow::InsertChatInput(std::string text) {
|
||||
}
|
||||
|
||||
Snowflake ChatWindow::GetOldestListedMessage() {
|
||||
return m_chat->GetOldestListedMessage();
|
||||
return m_id_to_widget.begin()->first;
|
||||
}
|
||||
|
||||
void ChatWindow::UpdateReactions(Snowflake id) {
|
||||
m_chat->UpdateMessageReactions(id);
|
||||
}
|
||||
|
||||
void ChatWindow::SetTopic(const std::string &text) {
|
||||
m_topic_text.set_text(text);
|
||||
m_topic.set_visible(text.length() > 0);
|
||||
auto it = m_id_to_widget.find(id);
|
||||
if (it == m_id_to_widget.end()) return;
|
||||
auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
|
||||
if (widget == nullptr) return;
|
||||
widget->UpdateReactions();
|
||||
}
|
||||
|
||||
Snowflake ChatWindow::GetActiveChannel() const {
|
||||
@@ -159,9 +200,6 @@ bool ChatWindow::OnInputSubmit(const Glib::ustring &text) {
|
||||
if (!m_rate_limit_indicator->CanSpeak())
|
||||
return false;
|
||||
|
||||
if (text.size() == 0)
|
||||
return false;
|
||||
|
||||
if (m_active_channel.IsValid())
|
||||
m_signal_action_chat_submit.emit(text, m_active_channel, m_replying_to); // m_replying_to is checked for invalid in the handler
|
||||
if (m_is_replying)
|
||||
@@ -180,6 +218,122 @@ bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChatMessageItemContainer *ChatWindow::CreateMessageComponent(Snowflake id) {
|
||||
auto *container = ChatMessageItemContainer::FromMessage(id);
|
||||
return container;
|
||||
}
|
||||
|
||||
void ChatWindow::RemoveMessageAndHeader(Gtk::Widget *widget) {
|
||||
ChatMessageHeader *header = dynamic_cast<ChatMessageHeader *>(widget->get_ancestor(Gtk::ListBoxRow::get_type()));
|
||||
if (header != nullptr) {
|
||||
if (header->GetChildContent().size() == 1) {
|
||||
m_num_rows--;
|
||||
delete header;
|
||||
} else
|
||||
delete widget;
|
||||
} else
|
||||
delete widget;
|
||||
m_num_messages--;
|
||||
}
|
||||
|
||||
constexpr static int MaxMessagesForCull = 50; // this has to be 50 cuz that magic number is used in a couple other places and i dont feel like replacing them
|
||||
void ChatWindow::ProcessNewMessage(Snowflake id, bool prepend) {
|
||||
const auto &client = Abaddon::Get().GetDiscordClient();
|
||||
if (!client.IsStarted()) return; // e.g. load channel and then dc
|
||||
const auto data = client.GetMessage(id);
|
||||
if (!data.has_value()) return;
|
||||
|
||||
if (!data->IsPending && data->Nonce.has_value() && data->Author.ID == client.GetUserData().ID) {
|
||||
for (auto [id, widget] : m_id_to_widget) {
|
||||
if (dynamic_cast<ChatMessageItemContainer *>(widget)->Nonce == *data->Nonce) {
|
||||
RemoveMessageAndHeader(widget);
|
||||
m_id_to_widget.erase(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessageHeader *last_row = nullptr;
|
||||
bool should_attach = false;
|
||||
if (m_num_rows > 0) {
|
||||
if (prepend)
|
||||
last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(0));
|
||||
else
|
||||
last_row = dynamic_cast<ChatMessageHeader *>(m_list->get_row_at_index(m_num_rows - 1));
|
||||
|
||||
if (last_row != nullptr) {
|
||||
const uint64_t diff = std::max(id, last_row->NewestID) - std::min(id, last_row->NewestID);
|
||||
if (last_row->UserID == data->Author.ID && (prepend || (diff < SnowflakeSplitDifference * Snowflake::SecondsInterval)))
|
||||
should_attach = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_num_messages++;
|
||||
|
||||
if (m_should_scroll_to_bottom && !prepend)
|
||||
while (m_num_messages > MaxMessagesForCull) {
|
||||
auto first_it = m_id_to_widget.begin();
|
||||
RemoveMessageAndHeader(first_it->second);
|
||||
m_id_to_widget.erase(first_it);
|
||||
}
|
||||
|
||||
ChatMessageHeader *header;
|
||||
if (should_attach) {
|
||||
header = last_row;
|
||||
} else {
|
||||
const auto guild_id = *client.GetChannel(m_active_channel)->GuildID;
|
||||
const auto user_id = data->Author.ID;
|
||||
const auto user = client.GetUser(user_id);
|
||||
if (!user.has_value()) return;
|
||||
|
||||
header = Gtk::manage(new ChatMessageHeader(&*data));
|
||||
header->signal_action_insert_mention().connect([this, user_id]() {
|
||||
m_signal_action_insert_mention.emit(user_id);
|
||||
});
|
||||
|
||||
header->signal_action_open_user_menu().connect([this, user_id, guild_id](const GdkEvent *event) {
|
||||
m_signal_action_open_user_menu.emit(event, user_id, guild_id);
|
||||
});
|
||||
|
||||
m_num_rows++;
|
||||
}
|
||||
|
||||
auto *content = CreateMessageComponent(id);
|
||||
if (content != nullptr) {
|
||||
header->AddContent(content, prepend);
|
||||
m_id_to_widget[id] = content;
|
||||
|
||||
if (!data->IsPending) {
|
||||
content->signal_action_delete().connect([this, id] {
|
||||
m_signal_action_message_delete.emit(m_active_channel, id);
|
||||
});
|
||||
content->signal_action_edit().connect([this, id] {
|
||||
m_signal_action_message_edit.emit(m_active_channel, id);
|
||||
});
|
||||
content->signal_action_reaction_add().connect([this, id](const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_add.emit(id, param);
|
||||
});
|
||||
content->signal_action_reaction_remove().connect([this, id](const Glib::ustring ¶m) {
|
||||
m_signal_action_reaction_remove.emit(id, param);
|
||||
});
|
||||
content->signal_action_channel_click().connect([this](const Snowflake &id) {
|
||||
m_signal_action_channel_click.emit(id);
|
||||
});
|
||||
content->signal_action_reply_to().connect(sigc::mem_fun(*this, &ChatWindow::StartReplying));
|
||||
}
|
||||
}
|
||||
|
||||
header->set_margin_left(5);
|
||||
header->show_all();
|
||||
|
||||
if (!should_attach) {
|
||||
if (prepend)
|
||||
m_list->prepend(*header);
|
||||
else
|
||||
m_list->add(*header);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWindow::StartReplying(Snowflake message_id) {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto message = *discord.GetMessage(message_id);
|
||||
@@ -206,8 +360,22 @@ void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) {
|
||||
m_signal_action_chat_load_history.emit(m_active_channel);
|
||||
}
|
||||
|
||||
void ChatWindow::ScrollToBottom() {
|
||||
auto x = m_scroll->get_vadjustment();
|
||||
x->set_value(x->get_upper());
|
||||
}
|
||||
|
||||
void ChatWindow::OnMessageSendFail(const std::string &nonce, float retry_after) {
|
||||
m_chat->SetFailedByNonce(nonce);
|
||||
for (auto [id, widget] : m_id_to_widget) {
|
||||
if (auto *container = dynamic_cast<ChatMessageItemContainer *>(widget); container->Nonce == nonce) {
|
||||
container->SetFailed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_message_delete ChatWindow::signal_action_message_delete() {
|
||||
return m_signal_action_message_delete;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_message_edit ChatWindow::signal_action_message_edit() {
|
||||
@@ -230,6 +398,10 @@ ChatWindow::type_signal_action_insert_mention ChatWindow::signal_action_insert_m
|
||||
return m_signal_action_insert_mention;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_open_user_menu ChatWindow::signal_action_open_user_menu() {
|
||||
return m_signal_action_open_user_menu;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_reaction_add ChatWindow::signal_action_reaction_add() {
|
||||
return m_signal_action_reaction_add;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ class ChatMessageItemContainer;
|
||||
class ChatInput;
|
||||
class ChatInputIndicator;
|
||||
class RateLimitIndicator;
|
||||
class ChatList;
|
||||
class ChatWindow {
|
||||
public:
|
||||
ChatWindow();
|
||||
@@ -19,24 +18,30 @@ public:
|
||||
Snowflake GetActiveChannel() const;
|
||||
|
||||
void Clear();
|
||||
void SetMessages(const std::vector<Message> &msgs); // clear contents and replace with given set
|
||||
void SetMessages(const std::set<Snowflake> &msgs); // clear contents and replace with given set
|
||||
void SetActiveChannel(Snowflake id);
|
||||
void AddNewMessage(const Message &data); // append new message to bottom
|
||||
void AddNewMessage(Snowflake id); // append new message to bottom
|
||||
void DeleteMessage(Snowflake id); // add [deleted] indicator
|
||||
void UpdateMessage(Snowflake id); // add [edited] indicator
|
||||
void AddNewHistory(const std::vector<Message> &msgs); // prepend messages
|
||||
void AddNewHistory(const std::vector<Snowflake> &id); // prepend messages
|
||||
void InsertChatInput(std::string text);
|
||||
Snowflake GetOldestListedMessage(); // oldest message that is currently in the ListBox
|
||||
void UpdateReactions(Snowflake id);
|
||||
void SetTopic(const std::string &text);
|
||||
|
||||
protected:
|
||||
ChatMessageItemContainer *CreateMessageComponent(Snowflake id); // to be inserted into header's content box
|
||||
void ProcessNewMessage(Snowflake id, bool prepend); // creates and adds components
|
||||
|
||||
bool m_is_replying = false;
|
||||
Snowflake m_replying_to;
|
||||
|
||||
void StartReplying(Snowflake message_id);
|
||||
void StopReplying();
|
||||
|
||||
int m_num_messages = 0;
|
||||
int m_num_rows = 0;
|
||||
std::map<Snowflake, Gtk::Widget *> m_id_to_widget;
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
bool OnInputSubmit(const Glib::ustring &text);
|
||||
@@ -44,16 +49,16 @@ protected:
|
||||
bool OnKeyPressEvent(GdkEventKey *e);
|
||||
void OnScrollEdgeOvershot(Gtk::PositionType pos);
|
||||
|
||||
void RemoveMessageAndHeader(Gtk::Widget *widget);
|
||||
|
||||
void ScrollToBottom();
|
||||
bool m_should_scroll_to_bottom = true;
|
||||
|
||||
void OnMessageSendFail(const std::string &nonce, float retry_after);
|
||||
|
||||
Gtk::Box *m_main;
|
||||
//Gtk::ListBox *m_list;
|
||||
//Gtk::ScrolledWindow *m_scroll;
|
||||
|
||||
Gtk::EventBox m_topic; // todo probably make everything else go on the stack
|
||||
Gtk::Label m_topic_text;
|
||||
|
||||
ChatList *m_chat;
|
||||
Gtk::ListBox *m_list;
|
||||
Gtk::ScrolledWindow *m_scroll;
|
||||
|
||||
ChatInput *m_input;
|
||||
|
||||
@@ -63,28 +68,34 @@ protected:
|
||||
Gtk::Box *m_meta;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete;
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_edit;
|
||||
typedef sigc::signal<void, std::string, Snowflake, Snowflake> type_signal_action_chat_submit;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_chat_load_history;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_channel_click;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_insert_mention;
|
||||
typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_open_user_menu;
|
||||
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_add;
|
||||
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_action_reaction_remove;
|
||||
|
||||
type_signal_action_message_delete signal_action_message_delete();
|
||||
type_signal_action_message_edit signal_action_message_edit();
|
||||
type_signal_action_chat_submit signal_action_chat_submit();
|
||||
type_signal_action_chat_load_history signal_action_chat_load_history();
|
||||
type_signal_action_channel_click signal_action_channel_click();
|
||||
type_signal_action_insert_mention signal_action_insert_mention();
|
||||
type_signal_action_open_user_menu signal_action_open_user_menu();
|
||||
type_signal_action_reaction_add signal_action_reaction_add();
|
||||
type_signal_action_reaction_remove signal_action_reaction_remove();
|
||||
|
||||
private:
|
||||
type_signal_action_message_delete m_signal_action_message_delete;
|
||||
type_signal_action_message_edit m_signal_action_message_edit;
|
||||
type_signal_action_chat_submit m_signal_action_chat_submit;
|
||||
type_signal_action_chat_load_history m_signal_action_chat_load_history;
|
||||
type_signal_action_channel_click m_signal_action_channel_click;
|
||||
type_signal_action_insert_mention m_signal_action_insert_mention;
|
||||
type_signal_action_open_user_menu m_signal_action_open_user_menu;
|
||||
type_signal_action_reaction_add m_signal_action_reaction_add;
|
||||
type_signal_action_reaction_remove m_signal_action_reaction_remove;
|
||||
};
|
||||
|
||||
@@ -291,7 +291,7 @@ bool MultiBackwardSearch(const Gtk::TextIter &iter, const Glib::ustring &chars,
|
||||
if (!iter.backward_search(tmp, flags, tstart, tend)) continue;
|
||||
// if previous found, compare to see if closer to out iter
|
||||
if (any) {
|
||||
if (tstart.get_offset() > out.get_offset())
|
||||
if (tstart > out)
|
||||
out = tstart;
|
||||
} else
|
||||
out = tstart;
|
||||
@@ -308,7 +308,7 @@ bool MultiForwardSearch(const Gtk::TextIter &iter, const Glib::ustring &chars, G
|
||||
if (!iter.forward_search(tmp, flags, tstart, tend)) continue;
|
||||
// if previous found, compare to see if closer to out iter
|
||||
if (any) {
|
||||
if (tstart.get_offset() < out.get_offset())
|
||||
if (tstart < out)
|
||||
out = tstart;
|
||||
} else
|
||||
out = tstart;
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
#include "friendslist.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
FriendsList::FriendsList()
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_filter_mode(FILTER_FRIENDS) {
|
||||
get_style_context()->add_class("friends-list");
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
|
||||
discord.signal_relationship_add().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipAdd));
|
||||
discord.signal_relationship_remove().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipRemove));
|
||||
|
||||
PopulateRelationships();
|
||||
signal_map().connect(sigc::mem_fun(*this, &FriendsList::PopulateRelationships));
|
||||
|
||||
constexpr static std::array<const char *, 4> strs = {
|
||||
"Friends",
|
||||
"Online",
|
||||
"Pending",
|
||||
"Blocked",
|
||||
};
|
||||
for (const auto &x : strs) {
|
||||
auto *btn = Gtk::manage(new Gtk::RadioButton(m_group, x));
|
||||
m_buttons.add(*btn);
|
||||
btn->show();
|
||||
btn->signal_toggled().connect([this, btn, str = x] {
|
||||
if (!btn->get_active()) return;
|
||||
switch (str[0]) { // hehe
|
||||
case 'F':
|
||||
m_filter_mode = FILTER_FRIENDS;
|
||||
break;
|
||||
case 'O':
|
||||
m_filter_mode = FILTER_ONLINE;
|
||||
break;
|
||||
case 'P':
|
||||
m_filter_mode = FILTER_PENDING;
|
||||
break;
|
||||
case 'B':
|
||||
m_filter_mode = FILTER_BLOCKED;
|
||||
break;
|
||||
}
|
||||
m_list.invalidate_filter();
|
||||
});
|
||||
}
|
||||
m_buttons.set_homogeneous(true);
|
||||
m_buttons.set_halign(Gtk::ALIGN_CENTER);
|
||||
|
||||
m_add.set_halign(Gtk::ALIGN_CENTER);
|
||||
m_add.set_margin_top(5);
|
||||
m_add.set_margin_bottom(5);
|
||||
|
||||
m_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
m_list.set_sort_func(sigc::mem_fun(*this, &FriendsList::ListSortFunc));
|
||||
m_list.set_filter_func(sigc::mem_fun(*this, &FriendsList::ListFilterFunc));
|
||||
m_list.set_selection_mode(Gtk::SELECTION_NONE);
|
||||
m_list.set_hexpand(true);
|
||||
m_list.set_vexpand(true);
|
||||
m_scroll.add(m_list);
|
||||
add(m_add);
|
||||
add(m_buttons);
|
||||
add(m_scroll);
|
||||
|
||||
m_add.show();
|
||||
m_scroll.show();
|
||||
m_buttons.show();
|
||||
m_list.show();
|
||||
}
|
||||
|
||||
FriendsListFriendRow *FriendsList::MakeRow(const UserData &user, RelationshipType type) {
|
||||
auto *row = Gtk::manage(new FriendsListFriendRow(type, user));
|
||||
row->signal_action_remove().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionRemove), user.ID));
|
||||
row->signal_action_accept().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionAccept), user.ID));
|
||||
return row;
|
||||
}
|
||||
|
||||
void FriendsList::OnRelationshipAdd(const RelationshipAddData &data) {
|
||||
for (auto *row_ : m_list.get_children()) {
|
||||
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
|
||||
if (row == nullptr || row->ID != data.ID) continue;
|
||||
delete row;
|
||||
break;
|
||||
}
|
||||
|
||||
auto *row = MakeRow(data.User, data.Type);
|
||||
m_list.add(*row);
|
||||
row->show();
|
||||
}
|
||||
|
||||
void FriendsList::OnRelationshipRemove(Snowflake id, RelationshipType type) {
|
||||
for (auto *row_ : m_list.get_children()) {
|
||||
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
|
||||
if (row == nullptr || row->ID != id) continue;
|
||||
delete row;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FriendsList::OnActionAccept(Snowflake id) {
|
||||
const auto cb = [this](DiscordError code) {
|
||||
if (code != DiscordError::NONE) {
|
||||
Gtk::MessageDialog dlg(*dynamic_cast<Gtk::Window *>(get_toplevel()), "Failed to accept", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().PutRelationship(id, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
void FriendsList::OnActionRemove(Snowflake id) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto user = discord.GetUser(id);
|
||||
if (auto *window = dynamic_cast<Gtk::Window *>(get_toplevel())) {
|
||||
Glib::ustring str;
|
||||
switch (*discord.GetRelationship(id)) {
|
||||
case RelationshipType::Blocked:
|
||||
str = "Are you sure you want to unblock " + user->Username + "#" + user->Discriminator + "?";
|
||||
break;
|
||||
case RelationshipType::Friend:
|
||||
str = "Are you sure you want to remove " + user->Username + "#" + user->Discriminator + "?";
|
||||
break;
|
||||
case RelationshipType::PendingIncoming:
|
||||
str = "Are you sure you want to ignore " + user->Username + "#" + user->Discriminator + "?";
|
||||
break;
|
||||
case RelationshipType::PendingOutgoing:
|
||||
str = "Are you sure you want to cancel your request to " + user->Username + "#" + user->Discriminator + "?";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (Abaddon::Get().ShowConfirm(str, window)) {
|
||||
const auto cb = [this, window](DiscordError code) {
|
||||
if (code == DiscordError::NONE) return;
|
||||
Gtk::MessageDialog dlg(*window, "Failed to remove user", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
};
|
||||
discord.RemoveRelationship(id, sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FriendsList::PopulateRelationships() {
|
||||
for (auto child : m_list.get_children())
|
||||
delete child;
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
for (const auto &[id, type] : discord.GetRelationships()) {
|
||||
const auto user = discord.GetUser(id);
|
||||
if (!user.has_value()) continue;
|
||||
auto *row = MakeRow(*user, type);
|
||||
m_list.add(*row);
|
||||
row->show();
|
||||
}
|
||||
}
|
||||
|
||||
int FriendsList::ListSortFunc(Gtk::ListBoxRow *a_, Gtk::ListBoxRow *b_) {
|
||||
auto *a = dynamic_cast<FriendsListFriendRow *>(a_);
|
||||
auto *b = dynamic_cast<FriendsListFriendRow *>(b_);
|
||||
if (a == nullptr || b == nullptr) return 0;
|
||||
return a->Name.compare(b->Name);
|
||||
}
|
||||
|
||||
bool FriendsList::ListFilterFunc(Gtk::ListBoxRow *row_) {
|
||||
auto *row = dynamic_cast<FriendsListFriendRow *>(row_);
|
||||
if (row == nullptr) return false;
|
||||
switch (m_filter_mode) {
|
||||
case FILTER_FRIENDS:
|
||||
return row->Type == RelationshipType::Friend;
|
||||
case FILTER_ONLINE:
|
||||
return row->Type == RelationshipType::Friend && row->Status != PresenceStatus::Offline;
|
||||
case FILTER_PENDING:
|
||||
return row->Type == RelationshipType::PendingIncoming || row->Type == RelationshipType::PendingOutgoing;
|
||||
case FILTER_BLOCKED:
|
||||
return row->Type == RelationshipType::Blocked;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FriendsListAddComponent::FriendsListAddComponent()
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_label("Add a Friend", Gtk::ALIGN_START)
|
||||
, m_box(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_add("Add")
|
||||
, m_status("", Gtk::ALIGN_START) {
|
||||
m_box.add(m_entry);
|
||||
m_box.add(m_add);
|
||||
m_box.add(m_status);
|
||||
|
||||
m_add.signal_clicked().connect(sigc::mem_fun(*this, &FriendsListAddComponent::Submit));
|
||||
|
||||
m_label.set_halign(Gtk::ALIGN_CENTER);
|
||||
|
||||
m_entry.set_placeholder_text("Enter a Username#1234");
|
||||
m_entry.signal_key_press_event().connect(sigc::mem_fun(*this, &FriendsListAddComponent::OnKeyPress), false);
|
||||
|
||||
add(m_label);
|
||||
add(m_box);
|
||||
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void FriendsListAddComponent::Submit() {
|
||||
if (m_requesting) return;
|
||||
|
||||
auto text = m_entry.get_text();
|
||||
m_label.set_text("Invalid input"); // cheeky !!
|
||||
m_entry.set_text("");
|
||||
const auto hashpos = text.find("#");
|
||||
if (hashpos == Glib::ustring::npos) return;
|
||||
const auto username = text.substr(0, hashpos);
|
||||
const auto discriminator = text.substr(hashpos + 1);
|
||||
if (username.size() == 0 || discriminator.size() != 4) return;
|
||||
if (discriminator.find_first_not_of("0123456789") != Glib::ustring::npos) return;
|
||||
|
||||
m_requesting = true;
|
||||
m_label.set_text("Hang on...");
|
||||
|
||||
const auto cb = [this](DiscordError code) {
|
||||
m_requesting = false;
|
||||
if (code == DiscordError::NONE) {
|
||||
m_label.set_text("Success!");
|
||||
} else {
|
||||
m_label.set_text("Failed: "s + GetDiscordErrorDisplayString(code));
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().SendFriendRequest(username, std::stoul(discriminator), sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
bool FriendsListAddComponent::OnKeyPress(GdkEventKey *e) {
|
||||
if (e->keyval == GDK_KEY_Return) {
|
||||
Submit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data)
|
||||
: Name(data.Username + "#" + data.Discriminator)
|
||||
, Type(type)
|
||||
, ID(data.ID)
|
||||
, Status(Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID))
|
||||
, m_accept("Accept") {
|
||||
auto *ev = Gtk::manage(new Gtk::EventBox);
|
||||
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
auto *img = Gtk::manage(new LazyImage(32, 32, true));
|
||||
auto *namebox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
auto *namelbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START));
|
||||
m_status_lbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START));
|
||||
auto *lblbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_presence_update().connect(sigc::mem_fun(*this, &FriendsListFriendRow::OnPresenceUpdate));
|
||||
|
||||
static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
if (data.HasAnimatedAvatar() && show_animations) {
|
||||
img->SetAnimated(true);
|
||||
img->SetURL(data.GetAvatarURL("gif", "32"));
|
||||
} else {
|
||||
img->SetURL(data.GetAvatarURL("png", "32"));
|
||||
}
|
||||
|
||||
namelbl->set_markup(data.GetEscapedBoldName());
|
||||
|
||||
UpdatePresenceLabel();
|
||||
|
||||
AddWidgetMenuHandler(ev, m_menu, [this] {
|
||||
m_accept.set_visible(Type == RelationshipType::PendingIncoming);
|
||||
|
||||
switch (Type) {
|
||||
case RelationshipType::Blocked:
|
||||
case RelationshipType::Friend:
|
||||
m_remove.set_label("Remove");
|
||||
break;
|
||||
case RelationshipType::PendingIncoming:
|
||||
m_remove.set_label("Ignore");
|
||||
break;
|
||||
case RelationshipType::PendingOutgoing:
|
||||
m_remove.set_label("Cancel");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
m_remove.signal_activate().connect([this] {
|
||||
m_signal_remove.emit();
|
||||
});
|
||||
|
||||
m_accept.signal_activate().connect([this] {
|
||||
m_signal_accept.emit();
|
||||
});
|
||||
|
||||
m_menu.append(m_accept);
|
||||
m_menu.append(m_remove);
|
||||
m_menu.show_all();
|
||||
|
||||
lblbox->set_valign(Gtk::ALIGN_CENTER);
|
||||
|
||||
img->set_margin_end(5);
|
||||
|
||||
namebox->add(*namelbl);
|
||||
lblbox->add(*namebox);
|
||||
lblbox->add(*m_status_lbl);
|
||||
|
||||
box->add(*img);
|
||||
box->add(*lblbox);
|
||||
|
||||
ev->add(*box);
|
||||
add(*ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void FriendsListFriendRow::UpdatePresenceLabel() {
|
||||
switch (Type) {
|
||||
case RelationshipType::PendingIncoming:
|
||||
m_status_lbl->set_text("Incoming Friend Request");
|
||||
break;
|
||||
case RelationshipType::PendingOutgoing:
|
||||
m_status_lbl->set_text("Outgoing Friend Request");
|
||||
break;
|
||||
default:
|
||||
m_status_lbl->set_text(GetPresenceDisplayString(Status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FriendsListFriendRow::OnPresenceUpdate(const UserData &user, PresenceStatus status) {
|
||||
if (user.ID != ID) return;
|
||||
Status = status;
|
||||
UpdatePresenceLabel();
|
||||
changed();
|
||||
}
|
||||
|
||||
FriendsListFriendRow::type_signal_remove FriendsListFriendRow::signal_action_remove() {
|
||||
return m_signal_remove;
|
||||
}
|
||||
|
||||
FriendsListFriendRow::type_signal_accept FriendsListFriendRow::signal_action_accept() {
|
||||
return m_signal_accept;
|
||||
}
|
||||
|
||||
FriendsListWindow::FriendsListWindow() {
|
||||
add(m_friends);
|
||||
set_default_size(500, 500);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
m_friends.show();
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "../discord/objects.hpp"
|
||||
|
||||
class FriendsListAddComponent : public Gtk::Box {
|
||||
public:
|
||||
FriendsListAddComponent();
|
||||
|
||||
private:
|
||||
void Submit();
|
||||
bool OnKeyPress(GdkEventKey *e);
|
||||
|
||||
Gtk::Label m_label;
|
||||
Gtk::Label m_status;
|
||||
Gtk::Entry m_entry;
|
||||
Gtk::Button m_add;
|
||||
Gtk::Box m_box;
|
||||
|
||||
bool m_requesting = false;
|
||||
};
|
||||
|
||||
class FriendsListFriendRow;
|
||||
class FriendsList : public Gtk::Box {
|
||||
public:
|
||||
FriendsList();
|
||||
|
||||
private:
|
||||
FriendsListFriendRow *MakeRow(const UserData &user, RelationshipType type);
|
||||
|
||||
void OnRelationshipAdd(const RelationshipAddData &data);
|
||||
void OnRelationshipRemove(Snowflake id, RelationshipType type);
|
||||
|
||||
void OnActionAccept(Snowflake id);
|
||||
void OnActionRemove(Snowflake id);
|
||||
|
||||
void PopulateRelationships();
|
||||
|
||||
enum FilterMode {
|
||||
FILTER_FRIENDS,
|
||||
FILTER_ONLINE,
|
||||
FILTER_PENDING,
|
||||
FILTER_BLOCKED,
|
||||
};
|
||||
|
||||
FilterMode m_filter_mode;
|
||||
|
||||
int ListSortFunc(Gtk::ListBoxRow *a, Gtk::ListBoxRow *b);
|
||||
bool ListFilterFunc(Gtk::ListBoxRow *row);
|
||||
|
||||
FriendsListAddComponent m_add;
|
||||
Gtk::RadioButtonGroup m_group;
|
||||
Gtk::ButtonBox m_buttons;
|
||||
Gtk::ScrolledWindow m_scroll;
|
||||
Gtk::ListBox m_list;
|
||||
};
|
||||
|
||||
class FriendsListFriendRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
FriendsListFriendRow(RelationshipType type, const UserData &str);
|
||||
|
||||
Snowflake ID;
|
||||
RelationshipType Type;
|
||||
Glib::ustring Name;
|
||||
PresenceStatus Status;
|
||||
|
||||
private:
|
||||
void UpdatePresenceLabel();
|
||||
void OnPresenceUpdate(const UserData &user, PresenceStatus status);
|
||||
|
||||
Gtk::Label *m_status_lbl;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem m_remove; // or cancel or ignore
|
||||
Gtk::MenuItem m_accept; // incoming
|
||||
|
||||
using type_signal_remove = sigc::signal<void>;
|
||||
using type_signal_accept = sigc::signal<void>;
|
||||
type_signal_remove m_signal_remove;
|
||||
type_signal_accept m_signal_accept;
|
||||
|
||||
public:
|
||||
type_signal_remove signal_action_remove();
|
||||
type_signal_accept signal_action_accept();
|
||||
};
|
||||
|
||||
class FriendsListWindow : public Gtk::Window {
|
||||
public:
|
||||
FriendsListWindow();
|
||||
|
||||
private:
|
||||
FriendsList m_friends;
|
||||
};
|
||||
8
components/inotifyswitched.hpp
Normal file
8
components/inotifyswitched.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
// for things that are used in stackswitchers to be able to be told when they're switched to
|
||||
|
||||
class INotifySwitched {
|
||||
public:
|
||||
virtual void on_switched_to() {};
|
||||
};
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
constexpr static const int MaxMemberListRows = 200;
|
||||
|
||||
MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data) {
|
||||
MemberListUserRow::MemberListUserRow(const GuildData *guild, const UserData &data) {
|
||||
ID = data.ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
@@ -15,10 +15,9 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
|
||||
m_status_indicator = Gtk::manage(new StatusIndicator(ID));
|
||||
|
||||
static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
|
||||
if (crown && guild.has_value() && guild->OwnerID == data.ID) {
|
||||
if (crown && guild != nullptr && guild->OwnerID == data.ID) {
|
||||
try {
|
||||
const static auto crown_path = Abaddon::GetResPath("/crown.png");
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(crown_path, 12, 12);
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file("./res/crown.png", 12, 12);
|
||||
m_crown = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
m_crown->set_valign(Gtk::ALIGN_CENTER);
|
||||
m_crown->set_margin_end(8);
|
||||
@@ -27,10 +26,7 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
|
||||
|
||||
m_status_indicator->set_margin_start(3);
|
||||
|
||||
if (guild.has_value())
|
||||
m_avatar->SetURL(data.GetAvatarURL(guild->ID, "png"));
|
||||
else
|
||||
m_avatar->SetURL(data.GetAvatarURL("png"));
|
||||
m_avatar->SetURL(data.GetAvatarURL("png"));
|
||||
|
||||
get_style_context()->add_class("members-row");
|
||||
get_style_context()->add_class("members-row-member");
|
||||
@@ -44,7 +40,7 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
|
||||
std::string display = data.Username;
|
||||
if (show_discriminator)
|
||||
display += "#" + data.Discriminator;
|
||||
if (guild.has_value()) {
|
||||
if (guild != nullptr) {
|
||||
if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) {
|
||||
auto color = Abaddon::Get().GetDiscordClient().GetRole(col_id)->Color;
|
||||
m_label->set_use_markup(true);
|
||||
@@ -94,7 +90,7 @@ void MemberList::SetActiveChannel(Snowflake id) {
|
||||
m_guild_id = Snowflake::Invalid;
|
||||
if (m_chan_id.IsValid()) {
|
||||
const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
if (chan.has_value() && chan->GuildID.has_value()) m_guild_id = *chan->GuildID;
|
||||
if (chan.has_value()) m_guild_id = *chan->GuildID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +114,7 @@ void MemberList::UpdateMemberList() {
|
||||
int num_rows = 0;
|
||||
for (const auto &user : chan->GetDMRecipients()) {
|
||||
if (num_rows++ > MaxMemberListRows) break;
|
||||
auto *row = Gtk::manage(new MemberListUserRow(std::nullopt, user));
|
||||
auto *row = Gtk::manage(new MemberListUserRow(nullptr, user));
|
||||
m_id_to_row[user.ID] = row;
|
||||
AttachUserMenuHandler(row, user.ID);
|
||||
m_listbox->add(*row);
|
||||
@@ -127,12 +123,7 @@ void MemberList::UpdateMemberList() {
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<Snowflake> ids;
|
||||
if (chan->IsThread()) {
|
||||
const auto x = discord.GetUsersInThread(m_chan_id);
|
||||
ids = { x.begin(), x.end() };
|
||||
} else
|
||||
ids = discord.GetUsersInGuild(m_guild_id);
|
||||
auto ids = discord.GetUsersInGuild(m_guild_id);
|
||||
|
||||
// process all the shit first so its in proper order
|
||||
std::map<int, RoleData> pos_to_role;
|
||||
@@ -165,7 +156,7 @@ void MemberList::UpdateMemberList() {
|
||||
const auto guild = *discord.GetGuild(m_guild_id);
|
||||
auto add_user = [this, &user_to_color, &num_rows, guild](const UserData &data) -> bool {
|
||||
if (num_rows++ > MaxMemberListRows) return false;
|
||||
auto *row = Gtk::manage(new MemberListUserRow(guild, data));
|
||||
auto *row = Gtk::manage(new MemberListUserRow(&guild, data));
|
||||
m_id_to_row[data.ID] = row;
|
||||
AttachUserMenuHandler(row, data.ID);
|
||||
m_listbox->add(*row);
|
||||
@@ -219,10 +210,14 @@ void MemberList::UpdateMemberList() {
|
||||
void MemberList::AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id) {
|
||||
row->signal_button_press_event().connect([this, row, id](GdkEventButton *e) -> bool {
|
||||
if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) {
|
||||
Abaddon::Get().ShowUserMenu(reinterpret_cast<const GdkEvent *>(e), id, m_guild_id);
|
||||
m_signal_action_show_user_menu.emit(reinterpret_cast<const GdkEvent *>(e), id, m_guild_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
MemberList::type_signal_action_show_user_menu MemberList::signal_action_show_user_menu() {
|
||||
return m_signal_action_show_user_menu;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class LazyImage;
|
||||
class StatusIndicator;
|
||||
class MemberListUserRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
MemberListUserRow(const std::optional<GuildData> &guild, const UserData &data);
|
||||
MemberListUserRow(const GuildData *guild, const UserData &data);
|
||||
|
||||
Snowflake ID;
|
||||
|
||||
@@ -41,4 +41,12 @@ private:
|
||||
Snowflake m_chan_id;
|
||||
|
||||
std::unordered_map<Snowflake, Gtk::ListBoxRow *> m_id_to_row;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, const GdkEvent *, Snowflake, Snowflake> type_signal_action_show_user_menu;
|
||||
|
||||
type_signal_action_show_user_menu signal_action_show_user_menu();
|
||||
|
||||
private:
|
||||
type_signal_action_show_user_menu m_signal_action_show_user_menu;
|
||||
};
|
||||
|
||||
@@ -15,10 +15,9 @@ RateLimitIndicator::RateLimitIndicator()
|
||||
add(m_img);
|
||||
m_label.show();
|
||||
|
||||
const static auto clock_path = Abaddon::GetResPath("/clock.png");
|
||||
if (std::filesystem::exists(clock_path)) {
|
||||
if (std::filesystem::exists("./res/clock.png")) {
|
||||
try {
|
||||
const auto pixbuf = Gdk::Pixbuf::create_from_file(clock_path);
|
||||
const auto pixbuf = Gdk::Pixbuf::create_from_file("./res/clock.png");
|
||||
int w, h;
|
||||
GetImageDimensions(pixbuf->get_width(), pixbuf->get_height(), w, h, 20, 10);
|
||||
m_img.property_pixbuf() = pixbuf->scale_simple(w, h, Gdk::INTERP_BILINEAR);
|
||||
@@ -32,9 +31,9 @@ RateLimitIndicator::RateLimitIndicator()
|
||||
|
||||
void RateLimitIndicator::SetActiveChannel(Snowflake id) {
|
||||
m_active_channel = id;
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
|
||||
if (channel.has_value() && channel->RateLimitPerUser.has_value())
|
||||
m_rate_limit = *channel->RateLimitPerUser;
|
||||
const auto channel = *Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
|
||||
if (channel.RateLimitPerUser.has_value())
|
||||
m_rate_limit = *channel.RateLimitPerUser;
|
||||
else
|
||||
m_rate_limit = 0;
|
||||
|
||||
@@ -106,7 +105,6 @@ bool RateLimitIndicator::UpdateIndicator() {
|
||||
void RateLimitIndicator::OnMessageCreate(const Message &message) {
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (message.Author.ID != discord.GetUserData().ID) return;
|
||||
if (!message.GuildID.has_value()) return;
|
||||
const bool can_bypass = discord.HasAnyChannelPermission(discord.GetUserData().ID, m_active_channel, Permission::MANAGE_MESSAGES | Permission::MANAGE_CHANNELS);
|
||||
const auto rate_limit = GetRateLimit();
|
||||
if (rate_limit > 0 && !can_bypass) {
|
||||
@@ -125,10 +123,7 @@ void RateLimitIndicator::OnMessageSendFail(const std::string &nonce, float retry
|
||||
}
|
||||
|
||||
void RateLimitIndicator::OnChannelUpdate(Snowflake channel_id) {
|
||||
if (channel_id != m_active_channel) return;
|
||||
const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(m_active_channel);
|
||||
if (!chan.has_value()) return;
|
||||
const auto r = chan->RateLimitPerUser;
|
||||
const auto r = Abaddon::Get().GetDiscordClient().GetChannel(channel_id)->RateLimitPerUser;
|
||||
if (r.has_value())
|
||||
m_rate_limit = *r;
|
||||
else
|
||||
|
||||
@@ -18,8 +18,8 @@ StatusIndicator::StatusIndicator(Snowflake user_id)
|
||||
get_style_context()->add_class("status-indicator");
|
||||
|
||||
Abaddon::Get().GetDiscordClient().signal_guild_member_list_update().connect(sigc::hide(sigc::mem_fun(*this, &StatusIndicator::CheckStatus)));
|
||||
auto cb = [this](const UserData &user, PresenceStatus status) {
|
||||
if (user.ID == m_id) CheckStatus();
|
||||
auto cb = [this](Snowflake id, PresenceStatus status) {
|
||||
if (id == m_id) CheckStatus();
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().signal_presence_update().connect(sigc::track_obj(cb, *this));
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#define ABADDON_DEFAULT_RESOURCE_DIR "@ABADDON_RESOURCE_DIR@"
|
||||
@@ -1,4 +0,0 @@
|
||||
#include <cstdint>
|
||||
|
||||
constexpr static uint64_t SnowflakeSplitDifference = 600;
|
||||
constexpr static int MaxMessagesForChatCull = 50; // this has to be 50 (for now) cuz that magic number is used in a couple other places and i dont feel like replacing them
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
application wide stuff
|
||||
has to be separate to allow main.css to override certain things
|
||||
*/
|
||||
|
||||
.app-window label:not(:disabled) {
|
||||
color: @text_color;
|
||||
}
|
||||
|
||||
.app-window entry {
|
||||
background: @secondary_color;
|
||||
color: @text_color;
|
||||
border: 1px solid #1c2e40;
|
||||
}
|
||||
|
||||
.app-window button {
|
||||
background: @secondary_color;
|
||||
color: @text_color;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.app-window button:checked {
|
||||
border-top: 0px;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-bottom: 3px solid #39a2ed;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.app-window button:not(:checked) {
|
||||
border: 3px #0000ff;
|
||||
}
|
||||
|
||||
.app-window.background {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
.app-window treeview {
|
||||
color: @text_color;
|
||||
}
|
||||
|
||||
.app-window treeview:not(:selected) {
|
||||
background: @secondary_color;
|
||||
}
|
||||
|
||||
.app-popup list {
|
||||
background: @secondary_color;
|
||||
}
|
||||
|
||||
.app-window paned separator {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
.app-window scrollbar {
|
||||
background: @background_color;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.app-window menubar, menu {
|
||||
background: @background_color;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.app-window textview text {
|
||||
caret-color: #ababab;
|
||||
}
|
||||
|
||||
.app-window check,
|
||||
.app-window radio {
|
||||
background-clip: padding-box;
|
||||
background: @secondary_color;
|
||||
border-color: #070707;
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
color: #dddddd;
|
||||
}
|
||||
|
||||
.app-window check:checked,
|
||||
.app-window radio:checked {
|
||||
background-clip: border-box;
|
||||
background: #0b4285;
|
||||
border-color: #092444;
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
color: #dddddd;
|
||||
}
|
||||
|
||||
.app-window colorswatch {
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
}
|
||||
121
css/main.css
121
css/main.css
@@ -57,8 +57,14 @@
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.message-container + .message-container {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.message-container-extra {
|
||||
color: #78909c;
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
.message-container-timestamp {
|
||||
@@ -66,8 +72,7 @@
|
||||
}
|
||||
|
||||
.message-text {
|
||||
/* this isnt stricly necessary but it fixes emoji clipping */
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.message-text:not(.failed) text, .message-reply {
|
||||
@@ -136,26 +141,6 @@
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.message-component {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.message-component.primary {
|
||||
background: #5865F2;
|
||||
}
|
||||
|
||||
.message-component.secondary, .message-component.link {
|
||||
background: #4F545C;
|
||||
}
|
||||
|
||||
.message-component.success {
|
||||
background: #43B581;
|
||||
}
|
||||
|
||||
.message-component.danger {
|
||||
background: #F04747;
|
||||
}
|
||||
|
||||
.reaction-box {
|
||||
padding: 2px 5px 2px 5px;
|
||||
margin: 0px 0px 0px 0px;
|
||||
@@ -191,6 +176,62 @@
|
||||
color: @text_color;
|
||||
}
|
||||
|
||||
.app-window label:not(:disabled) {
|
||||
color: @text_color;
|
||||
}
|
||||
|
||||
.app-window entry {
|
||||
background: @secondary_color;
|
||||
color: @text_color;
|
||||
border: 1px solid #1c2e40;
|
||||
}
|
||||
|
||||
.app-window button {
|
||||
background: @secondary_color;
|
||||
color: @text_color;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.app-window button:checked {
|
||||
border-top: 0px;
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
border-bottom: 3px solid #39a2ed;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.app-window button:not(:checked) {
|
||||
border: 3px #0000ff;
|
||||
}
|
||||
|
||||
.app-window.background {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
.app-window treeview {
|
||||
color: @text_color;
|
||||
background: @secondary_color;
|
||||
}
|
||||
|
||||
.app-popup list {
|
||||
background: @secondary_color;
|
||||
}
|
||||
|
||||
.app-window paned separator {
|
||||
background: @background_color;
|
||||
}
|
||||
|
||||
.app-window scrollbar {
|
||||
background: @background_color;
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
.app-window menubar, menu {
|
||||
background: @background_color;
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.status-indicator.dnd {
|
||||
color: #982929;
|
||||
}
|
||||
@@ -262,10 +303,37 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.app-window textview text {
|
||||
caret-color: #ababab;
|
||||
}
|
||||
|
||||
.guild-members-pane-info {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
.app-window check,
|
||||
.app-window radio {
|
||||
background-clip: padding-box;
|
||||
background: @secondary_color;
|
||||
border-color: #070707;
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
color: #dddddd;
|
||||
}
|
||||
|
||||
.app-window check:checked,
|
||||
.app-window radio:checked {
|
||||
background-clip: border-box;
|
||||
background: #0b4285;
|
||||
border-color: #092444;
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
color: #dddddd;
|
||||
}
|
||||
|
||||
.app-window colorswatch {
|
||||
box-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.drag-hover-top {
|
||||
background: linear-gradient(to bottom, rgba(255, 66, 66, 0.65) 0%, rgba(0, 0, 0, 0) 35%);
|
||||
}
|
||||
@@ -273,12 +341,3 @@
|
||||
.drag-hover-bottom {
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(255, 66, 66, 0.65) 100%);
|
||||
}
|
||||
|
||||
.friends-list list {
|
||||
background: @background_color;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.friends-list-row-bot {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
#include "token.hpp"
|
||||
|
||||
std::string trim(const std::string& str) {
|
||||
const auto first = str.find_first_not_of(' ');
|
||||
if (first == std::string::npos) return str;
|
||||
const auto last = str.find_last_not_of(' ');
|
||||
return str.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
TokenDialog::TokenDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Set Token", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
@@ -18,7 +11,7 @@ TokenDialog::TokenDialog(Gtk::Window &parent)
|
||||
get_style_context()->add_class("app-popup");
|
||||
|
||||
m_ok.signal_clicked().connect([&]() {
|
||||
m_token = trim(m_entry.get_text());
|
||||
m_token = m_entry.get_text();
|
||||
response(Gtk::RESPONSE_OK);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "verificationgate.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../../abaddon.hpp"
|
||||
|
||||
VerificationGateDialog::VerificationGateDialog(Gtk::Window &parent, Snowflake guild_id)
|
||||
: Gtk::Dialog("Verification Required", parent, true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <optional>
|
||||
#include "../discord/objects.hpp"
|
||||
#include "../../discord/objects.hpp"
|
||||
|
||||
class VerificationGateDialog : public Gtk::Dialog {
|
||||
public:
|
||||
|
||||
@@ -26,20 +26,6 @@ constexpr inline const char *GetPresenceString(PresenceStatus s) {
|
||||
return "";
|
||||
}
|
||||
|
||||
constexpr inline const char* GetPresenceDisplayString(PresenceStatus s) {
|
||||
switch (s) {
|
||||
case PresenceStatus::Online:
|
||||
return "Online";
|
||||
case PresenceStatus::Offline:
|
||||
return "Offline";
|
||||
case PresenceStatus::Idle:
|
||||
return "Away";
|
||||
case PresenceStatus::DND:
|
||||
return "Do Not Disturb";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
enum class ActivityType : int {
|
||||
Game = 0,
|
||||
Streaming = 1,
|
||||
|
||||
@@ -20,7 +20,7 @@ void from_json(const nlohmann::json &j, AuditLogOptions &m) {
|
||||
void from_json(const nlohmann::json &j, AuditLogEntry &m) {
|
||||
JS_N("target_id", m.TargetID);
|
||||
JS_O("changes", m.Changes);
|
||||
JS_N("user_id", m.UserID);
|
||||
JS_D("user_id", m.UserID);
|
||||
JS_D("id", m.ID);
|
||||
JS_D("action_type", m.Type);
|
||||
JS_O("options", m.Options);
|
||||
|
||||
@@ -40,15 +40,6 @@ enum class AuditLogActionType {
|
||||
INTEGRATION_CREATE = 80,
|
||||
INTEGRATION_UPDATE = 81,
|
||||
INTEGRATION_DELETE = 82,
|
||||
STAGE_INSTANCE_CREATE = 83,
|
||||
STAGE_INSTANCE_UPDATE = 84,
|
||||
STAGE_INSTANCE_DELETE = 85,
|
||||
STICKER_CREATE = 90,
|
||||
STICKER_UPDATE = 91,
|
||||
STICKER_DELETE = 92,
|
||||
THREAD_CREATE = 110,
|
||||
THREAD_UPDATE = 111,
|
||||
THREAD_DELETE = 112,
|
||||
};
|
||||
|
||||
struct AuditLogChange {
|
||||
@@ -75,7 +66,7 @@ struct AuditLogOptions {
|
||||
struct AuditLogEntry {
|
||||
Snowflake ID;
|
||||
std::string TargetID; // null
|
||||
std::optional<Snowflake> UserID;
|
||||
Snowflake UserID;
|
||||
AuditLogActionType Type;
|
||||
std::optional<std::string> Reason;
|
||||
std::optional<std::vector<AuditLogChange>> Changes;
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
#include "../abaddon.hpp"
|
||||
#include "channel.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMetadataData &m) {
|
||||
JS_D("archived", m.IsArchived);
|
||||
JS_D("auto_archive_duration", m.AutoArchiveDuration);
|
||||
JS_D("archive_timestamp", m.ArchiveTimestamp);
|
||||
JS_O("locked", m.IsLocked);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMemberObject &m) {
|
||||
JS_O("id", m.ThreadID);
|
||||
JS_O("user_id", m.UserID);
|
||||
JS_D("join_timestamp", m.JoinTimestamp);
|
||||
JS_D("flags", m.Flags);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ChannelData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("type", m.Type);
|
||||
@@ -35,8 +21,6 @@ void from_json(const nlohmann::json &j, ChannelData &m) {
|
||||
JS_O("application_id", m.ApplicationID);
|
||||
JS_ON("parent_id", m.ParentID);
|
||||
JS_ON("last_pin_timestamp", m.LastPinTimestamp);
|
||||
JS_O("thread_metadata", m.ThreadMetadata);
|
||||
JS_O("member", m.ThreadMember);
|
||||
}
|
||||
|
||||
void ChannelData::update_from_json(const nlohmann::json &j) {
|
||||
@@ -59,20 +43,6 @@ void ChannelData::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("last_pin_timestamp", LastPinTimestamp);
|
||||
}
|
||||
|
||||
bool ChannelData::NSFW() const {
|
||||
return IsNSFW.has_value() && *IsNSFW;
|
||||
}
|
||||
|
||||
bool ChannelData::IsThread() const noexcept {
|
||||
return Type == ChannelType::GUILD_PUBLIC_THREAD ||
|
||||
Type == ChannelType::GUILD_PRIVATE_THREAD ||
|
||||
Type == ChannelType::GUILD_NEWS_THREAD;
|
||||
}
|
||||
|
||||
bool ChannelData::IsJoinedThread() const {
|
||||
return Abaddon::Get().GetDiscordClient().IsThreadJoined(ID);
|
||||
}
|
||||
|
||||
std::optional<PermissionOverwrite> ChannelData::GetOverwrite(Snowflake id) const {
|
||||
return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id);
|
||||
}
|
||||
|
||||
@@ -15,49 +15,12 @@ enum class ChannelType : int {
|
||||
GUILD_NEWS = 5,
|
||||
GUILD_STORE = 6,
|
||||
/* 7 and 8 were used for LFG */
|
||||
/* 9 was used for threads */
|
||||
GUILD_NEWS_THREAD = 10,
|
||||
GUILD_PUBLIC_THREAD = 11,
|
||||
GUILD_PRIVATE_THREAD = 12,
|
||||
/* 9 and 10 were used for threads */
|
||||
PUBLIC_THREAD = 11,
|
||||
PRIVATE_THREAD = 12,
|
||||
GUILD_STAGE_VOICE = 13,
|
||||
};
|
||||
|
||||
enum class StagePrivacy {
|
||||
PUBLIC = 1,
|
||||
GUILD_ONLY = 2,
|
||||
};
|
||||
|
||||
constexpr const char *GetStagePrivacyDisplayString(StagePrivacy e) {
|
||||
switch (e) {
|
||||
case StagePrivacy::PUBLIC:
|
||||
return "Public";
|
||||
case StagePrivacy::GUILD_ONLY:
|
||||
return "Guild Only";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// should be moved somewhere?
|
||||
|
||||
struct ThreadMetadataData {
|
||||
bool IsArchived;
|
||||
int AutoArchiveDuration;
|
||||
std::string ArchiveTimestamp;
|
||||
std::optional<bool> IsLocked;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadMetadataData &m);
|
||||
};
|
||||
|
||||
struct ThreadMemberObject {
|
||||
std::optional<Snowflake> ThreadID;
|
||||
std::optional<Snowflake> UserID;
|
||||
std::string JoinTimestamp;
|
||||
int Flags;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadMemberObject &m);
|
||||
};
|
||||
|
||||
struct ChannelData {
|
||||
Snowflake ID;
|
||||
ChannelType Type;
|
||||
@@ -78,15 +41,10 @@ struct ChannelData {
|
||||
std::optional<Snowflake> ApplicationID;
|
||||
std::optional<Snowflake> ParentID; // null
|
||||
std::optional<std::string> LastPinTimestamp; // null
|
||||
std::optional<ThreadMetadataData> ThreadMetadata;
|
||||
std::optional<ThreadMemberObject> ThreadMember;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ChannelData &m);
|
||||
void update_from_json(const nlohmann::json &j);
|
||||
|
||||
bool NSFW() const;
|
||||
bool IsThread() const noexcept;
|
||||
bool IsJoinedThread() const;
|
||||
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
|
||||
std::vector<UserData> GetDMRecipients() const;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,17 +6,20 @@
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <thread>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <mutex>
|
||||
#include <zlib.h>
|
||||
#include <glibmm.h>
|
||||
#include <queue>
|
||||
|
||||
// bruh
|
||||
#ifdef GetMessage
|
||||
#undef GetMessage
|
||||
#endif
|
||||
|
||||
// https://stackoverflow.com/questions/29775153/stopping-long-sleep-threads/29775639#29775639
|
||||
class HeartbeatWaiter {
|
||||
public:
|
||||
template<class R, class P>
|
||||
@@ -46,6 +49,10 @@ class Abaddon;
|
||||
class DiscordClient {
|
||||
friend class Abaddon;
|
||||
|
||||
public:
|
||||
static const constexpr char *DiscordGateway = "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream";
|
||||
static const constexpr char *DiscordAPI = "https://discord.com/api/v9";
|
||||
|
||||
public:
|
||||
DiscordClient(bool mem_store = false);
|
||||
void Start();
|
||||
@@ -65,14 +72,13 @@ public:
|
||||
const UserData &GetUserData() const;
|
||||
const UserSettings &GetUserSettings() const;
|
||||
std::vector<Snowflake> GetUserSortedGuilds() const;
|
||||
std::vector<Message> GetMessagesForChannel(Snowflake id, size_t limit = 50) const;
|
||||
std::vector<Snowflake> GetMessageIDsForChannel(Snowflake id) const;
|
||||
std::set<Snowflake> GetMessagesForChannel(Snowflake id) const;
|
||||
std::set<Snowflake> GetPrivateChannels() const;
|
||||
|
||||
EPremiumType GetSelfPremiumType() const;
|
||||
|
||||
void FetchMessagesInChannel(Snowflake id, sigc::slot<void(const std::vector<Message> &)> cb);
|
||||
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, sigc::slot<void(const std::vector<Message> &)> cb);
|
||||
void FetchMessagesInChannel(Snowflake id, std::function<void(const std::vector<Snowflake> &)> cb);
|
||||
void FetchMessagesInChannelBefore(Snowflake channel_id, Snowflake before_id, std::function<void(const std::vector<Snowflake> &)> cb);
|
||||
std::optional<Message> GetMessage(Snowflake id) const;
|
||||
std::optional<ChannelData> GetChannel(Snowflake id) const;
|
||||
std::optional<EmojiData> GetEmoji(Snowflake id) const;
|
||||
@@ -84,13 +90,9 @@ public:
|
||||
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
|
||||
Snowflake GetMemberHoistedRole(Snowflake guild_id, Snowflake user_id, bool with_color = false) const;
|
||||
std::optional<RoleData> GetMemberHighestRole(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::set<Snowflake> GetUsersInGuild(Snowflake id) const;
|
||||
std::set<Snowflake> GetChannelsInGuild(Snowflake id) const;
|
||||
std::vector<Snowflake> GetUsersInThread(Snowflake id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
|
||||
void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
|
||||
std::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
|
||||
std::unordered_set<Snowflake> GetChannelsInGuild(Snowflake id) const;
|
||||
|
||||
bool IsThreadJoined(Snowflake thread_id) const;
|
||||
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
|
||||
|
||||
bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
|
||||
@@ -106,7 +108,6 @@ public:
|
||||
void DeleteMessage(Snowflake channel_id, Snowflake id);
|
||||
void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
|
||||
void SendLazyLoad(Snowflake id);
|
||||
void SendThreadLazyLoad(Snowflake id);
|
||||
void JoinGuild(std::string code);
|
||||
void LeaveGuild(Snowflake id);
|
||||
void KickUser(Snowflake user_id, Snowflake guild_id);
|
||||
@@ -114,51 +115,40 @@ public:
|
||||
void UpdateStatus(PresenceStatus status, bool is_afk);
|
||||
void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj);
|
||||
void CreateDM(Snowflake user_id);
|
||||
void CreateDM(Snowflake user_id, sigc::slot<void(DiscordError code, Snowflake channel_id)> callback);
|
||||
void CreateDM(Snowflake user_id, sigc::slot<void(bool success, Snowflake channel_id)> callback);
|
||||
void CloseDM(Snowflake channel_id);
|
||||
std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms
|
||||
void AddReaction(Snowflake id, Glib::ustring param);
|
||||
void RemoveReaction(Snowflake id, Glib::ustring param);
|
||||
void SetGuildName(Snowflake id, const Glib::ustring &name);
|
||||
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
|
||||
void SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
|
||||
void SetGuildIcon(Snowflake id, const std::string &data);
|
||||
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(DiscordError code)> callback);
|
||||
void SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback);
|
||||
void UnbanUser(Snowflake guild_id, Snowflake user_id);
|
||||
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback);
|
||||
void DeleteInvite(const std::string &code);
|
||||
void DeleteInvite(const std::string &code, sigc::slot<void(DiscordError code)> callback);
|
||||
void DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback);
|
||||
void AddGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
|
||||
void RemoveGroupDMRecipient(Snowflake channel_id, Snowflake user_id);
|
||||
void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(DiscordError code)> callback);
|
||||
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void ModifyRolePermissions(Snowflake guild_id, Snowflake role_id, Permission permissions, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleName(Snowflake guild_id, Snowflake role_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, uint32_t color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::RGBA color, sigc::slot<void(bool success)> callback);
|
||||
void ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(bool success)> callback);
|
||||
void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback);
|
||||
void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(bool success)> callback);
|
||||
std::optional<GuildApplicationData> GetGuildApplication(Snowflake guild_id) const;
|
||||
void RemoveRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback);
|
||||
void SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot<void(DiscordError code)> callback);
|
||||
void PutRelationship(Snowflake id, sigc::slot<void(DiscordError code)> callback); // send fr by id, accept incoming
|
||||
void Pin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void Unpin(Snowflake channel_id, Snowflake message_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot<void(DiscordError code)> callback);
|
||||
void ArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
void UnArchiveThread(Snowflake channel_id, sigc::slot<void(DiscordError code)> callback);
|
||||
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const;
|
||||
bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const;
|
||||
|
||||
// real client doesn't seem to use the single role endpoints so neither do we
|
||||
template<typename Iter>
|
||||
auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot<void(DiscordError code)> callback) {
|
||||
auto SetMemberRoles(Snowflake guild_id, Snowflake user_id, Iter begin, Iter end, sigc::slot<void(bool success)> callback) {
|
||||
ModifyGuildMemberObject obj;
|
||||
obj.Roles = { begin, end };
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(guild_id) + "/members/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
|
||||
if (CheckCode(response))
|
||||
callback(DiscordError::NONE);
|
||||
else
|
||||
callback(GetCodeFromResponse(response));
|
||||
callback(CheckCode(response, 200));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,23 +167,18 @@ public:
|
||||
void FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback);
|
||||
void FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback);
|
||||
void SetUserNote(Snowflake user_id, std::string note);
|
||||
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(DiscordError code)> callback);
|
||||
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback);
|
||||
void FetchUserRelationships(Snowflake user_id, sigc::slot<void(std::vector<UserData>)> callback);
|
||||
|
||||
void FetchPinned(Snowflake id, sigc::slot<void(std::vector<Message>, DiscordError code)> callback);
|
||||
|
||||
bool IsVerificationRequired(Snowflake guild_id);
|
||||
void GetVerificationGateInfo(Snowflake guild_id, sigc::slot<void(std::optional<VerificationGateInfoObject>)> callback);
|
||||
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot<void(DiscordError code)> callback);
|
||||
void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, sigc::slot<void(bool success)> callback);
|
||||
|
||||
void UpdateToken(std::string token);
|
||||
void SetUserAgent(std::string agent);
|
||||
|
||||
PresenceStatus GetUserStatus(Snowflake id) const;
|
||||
|
||||
std::map<Snowflake, RelationshipType> GetRelationships() const;
|
||||
std::set<Snowflake> GetRelationships(RelationshipType type) const;
|
||||
std::optional<RelationshipType> GetRelationship(Snowflake id) const;
|
||||
std::unordered_set<Snowflake> GetRelationships(RelationshipType type) const;
|
||||
|
||||
private:
|
||||
static const constexpr int InflateChunkSize = 0x10000;
|
||||
@@ -201,11 +186,6 @@ private:
|
||||
std::vector<uint8_t> m_decompress_buf;
|
||||
z_stream m_zstream;
|
||||
|
||||
std::string GetAPIURL();
|
||||
std::string GetGatewayURL();
|
||||
|
||||
static DiscordError GetCodeFromResponse(const http::response_type &response);
|
||||
|
||||
void ProcessNewGuild(GuildData &guild);
|
||||
|
||||
void HandleGatewayMessageRaw(std::string str);
|
||||
@@ -242,15 +222,6 @@ private:
|
||||
void HandleGatewayGuildJoinRequestCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildJoinRequestUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayRelationshipRemove(const GatewayMessage &msg);
|
||||
void HandleGatewayRelationshipAdd(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadListSync(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadMembersUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadMemberUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
|
||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
@@ -268,14 +239,18 @@ private:
|
||||
|
||||
std::string m_token;
|
||||
|
||||
void AddMessageToChannel(Snowflake msg_id, Snowflake channel_id);
|
||||
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_chan_to_message_map;
|
||||
|
||||
void AddUserToGuild(Snowflake user_id, Snowflake guild_id);
|
||||
std::map<Snowflake, std::set<Snowflake>> m_guild_to_users;
|
||||
std::map<Snowflake, std::set<Snowflake>> m_guild_to_channels;
|
||||
std::map<Snowflake, GuildApplicationData> m_guild_join_requests;
|
||||
std::map<Snowflake, PresenceStatus> m_user_to_status;
|
||||
std::map<Snowflake, RelationshipType> m_user_relationships;
|
||||
std::set<Snowflake> m_joined_threads;
|
||||
std::map<Snowflake, std::vector<Snowflake>> m_thread_members;
|
||||
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_users;
|
||||
|
||||
std::unordered_map<Snowflake, std::unordered_set<Snowflake>> m_guild_to_channels;
|
||||
std::unordered_map<Snowflake, GuildApplicationData> m_guild_join_requests;
|
||||
|
||||
std::unordered_map<Snowflake, PresenceStatus> m_user_to_status;
|
||||
|
||||
std::unordered_map<Snowflake, RelationshipType> m_user_relationships;
|
||||
|
||||
UserData m_user_data;
|
||||
UserSettings m_user_settings;
|
||||
@@ -285,9 +260,8 @@ private:
|
||||
Websocket m_websocket;
|
||||
std::atomic<bool> m_client_connected = false;
|
||||
std::atomic<bool> m_ready_received = false;
|
||||
bool m_client_started = false;
|
||||
|
||||
std::map<std::string, GatewayEvent> m_event_map;
|
||||
std::unordered_map<std::string, GatewayEvent> m_event_map;
|
||||
void LoadEventMap();
|
||||
|
||||
std::thread m_heartbeat_thread;
|
||||
@@ -309,9 +283,6 @@ private:
|
||||
Glib::Dispatcher m_generic_dispatch;
|
||||
std::queue<std::function<void()>> m_generic_queue;
|
||||
|
||||
std::set<Snowflake> m_channels_pinned_requested;
|
||||
std::set<Snowflake> m_channels_lazy_loaded;
|
||||
|
||||
// signals
|
||||
public:
|
||||
typedef sigc::signal<void> type_signal_gateway_ready;
|
||||
@@ -323,7 +294,7 @@ public:
|
||||
typedef sigc::signal<void, Snowflake> type_signal_guild_delete;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_delete;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_update;
|
||||
typedef sigc::signal<void, ChannelData> type_signal_channel_create;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_channel_create;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_guild_update;
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_update; // guild id, role id
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_create; // guild id, role id
|
||||
@@ -336,30 +307,15 @@ public:
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_add; // guild id, user id
|
||||
typedef sigc::signal<void, InviteData> type_signal_invite_create;
|
||||
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
|
||||
typedef sigc::signal<void, UserData, PresenceStatus> type_signal_presence_update;
|
||||
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
|
||||
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
|
||||
typedef sigc::signal<void, Snowflake, std::vector<EmojiData>> type_signal_guild_emojis_update; // guild id
|
||||
typedef sigc::signal<void, GuildJoinRequestCreateData> type_signal_guild_join_request_create;
|
||||
typedef sigc::signal<void, GuildJoinRequestUpdateData> type_signal_guild_join_request_update;
|
||||
typedef sigc::signal<void, GuildJoinRequestDeleteData> type_signal_guild_join_request_delete;
|
||||
typedef sigc::signal<void, Snowflake, RelationshipType> type_signal_relationship_remove;
|
||||
typedef sigc::signal<void, RelationshipAddData> type_signal_relationship_add;
|
||||
typedef sigc::signal<void, ChannelData> type_signal_thread_create;
|
||||
typedef sigc::signal<void, ThreadDeleteData> type_signal_thread_delete;
|
||||
typedef sigc::signal<void, ThreadListSyncData> type_signal_thread_list_sync;
|
||||
typedef sigc::signal<void, ThreadMembersUpdateData> type_signal_thread_members_update;
|
||||
typedef sigc::signal<void, ThreadUpdateData> type_signal_thread_update;
|
||||
typedef sigc::signal<void, ThreadMemberListUpdateData> type_signal_thread_member_list_update;
|
||||
|
||||
// not discord dispatch events
|
||||
typedef sigc::signal<void, Snowflake> type_signal_added_to_thread;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_removed_from_thread;
|
||||
typedef sigc::signal<void, Message> type_signal_message_unpinned;
|
||||
typedef sigc::signal<void, Message> type_signal_message_pinned;
|
||||
typedef sigc::signal<void, Message> type_signal_message_sent;
|
||||
|
||||
typedef sigc::signal<void, std::string /* nonce */, float /* retry_after */> type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
typedef sigc::signal<void> type_signal_connected;
|
||||
|
||||
type_signal_gateway_ready signal_gateway_ready();
|
||||
@@ -390,19 +346,6 @@ public:
|
||||
type_signal_guild_join_request_create signal_guild_join_request_create();
|
||||
type_signal_guild_join_request_update signal_guild_join_request_update();
|
||||
type_signal_guild_join_request_delete signal_guild_join_request_delete();
|
||||
type_signal_relationship_remove signal_relationship_remove();
|
||||
type_signal_relationship_add signal_relationship_add();
|
||||
type_signal_message_unpinned signal_message_unpinned();
|
||||
type_signal_message_pinned signal_message_pinned();
|
||||
type_signal_thread_create signal_thread_create();
|
||||
type_signal_thread_delete signal_thread_delete();
|
||||
type_signal_thread_list_sync signal_thread_list_sync();
|
||||
type_signal_thread_members_update signal_thread_members_update();
|
||||
type_signal_thread_update signal_thread_update();
|
||||
type_signal_thread_member_list_update signal_thread_member_list_update();
|
||||
|
||||
type_signal_added_to_thread signal_added_to_thread();
|
||||
type_signal_removed_from_thread signal_removed_from_thread();
|
||||
type_signal_message_sent signal_message_sent();
|
||||
type_signal_message_send_fail signal_message_send_fail();
|
||||
type_signal_disconnected signal_disconnected();
|
||||
@@ -437,19 +380,6 @@ protected:
|
||||
type_signal_guild_join_request_create m_signal_guild_join_request_create;
|
||||
type_signal_guild_join_request_update m_signal_guild_join_request_update;
|
||||
type_signal_guild_join_request_delete m_signal_guild_join_request_delete;
|
||||
type_signal_relationship_remove m_signal_relationship_remove;
|
||||
type_signal_relationship_add m_signal_relationship_add;
|
||||
type_signal_message_unpinned m_signal_message_unpinned;
|
||||
type_signal_message_pinned m_signal_message_pinned;
|
||||
type_signal_thread_create m_signal_thread_create;
|
||||
type_signal_thread_delete m_signal_thread_delete;
|
||||
type_signal_thread_list_sync m_signal_thread_list_sync;
|
||||
type_signal_thread_members_update m_signal_thread_members_update;
|
||||
type_signal_thread_update m_signal_thread_update;
|
||||
type_signal_thread_member_list_update m_signal_thread_member_list_update;
|
||||
|
||||
type_signal_removed_from_thread m_signal_removed_from_thread;
|
||||
type_signal_added_to_thread m_signal_added_to_thread;
|
||||
type_signal_message_sent m_signal_message_sent;
|
||||
type_signal_message_send_fail m_signal_message_send_fail;
|
||||
type_signal_disconnected m_signal_disconnected;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
enum class DiscordError {
|
||||
GENERIC = 0,
|
||||
INVALID_FORM_BODY = 50035,
|
||||
RELATIONSHIP_INCOMING_DISABLED = 80000,
|
||||
RELATIONSHIP_INCOMING_BLOCKED = 80001,
|
||||
RELATIONSHIP_INVALID_USER_BOT = 80002, // this is misspelled in discord's source lul
|
||||
RELATIONSHIP_INVALID_SELF = 80003,
|
||||
RELATIONSHIP_INVALID_DISCORD_TAG = 80004,
|
||||
RELATIONSHIP_ALREADY_FRIENDS = 80007,
|
||||
|
||||
NONE = -1,
|
||||
};
|
||||
|
||||
constexpr const char *GetDiscordErrorDisplayString(DiscordError error) {
|
||||
switch (error) {
|
||||
case DiscordError::INVALID_FORM_BODY:
|
||||
return "Something's wrong with your input";
|
||||
case DiscordError::RELATIONSHIP_INCOMING_DISABLED:
|
||||
return "This user isn't accepting friend requests";
|
||||
case DiscordError::RELATIONSHIP_INCOMING_BLOCKED:
|
||||
return "You are blocked by this user";
|
||||
case DiscordError::RELATIONSHIP_INVALID_USER_BOT:
|
||||
return "You can't send a request to a bot";
|
||||
case DiscordError::RELATIONSHIP_INVALID_SELF:
|
||||
return "You can't send a request to yourself";
|
||||
case DiscordError::RELATIONSHIP_INVALID_DISCORD_TAG:
|
||||
return "No users with that tag exist";
|
||||
case DiscordError::RELATIONSHIP_ALREADY_FRIENDS:
|
||||
return "You are already friends with that user";
|
||||
case DiscordError::GENERIC:
|
||||
default:
|
||||
return "An error occurred";
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ void from_json(const nlohmann::json &j, GuildData &m) {
|
||||
// JS_O("voice_states", m.VoiceStates);
|
||||
// JS_O("members", m.Members);
|
||||
JS_O("channels", m.Channels);
|
||||
JS_O("threads", m.Threads);
|
||||
// JS_O("presences", m.Presences);
|
||||
JS_ON("max_presences", m.MaxPresences);
|
||||
JS_O("max_members", m.MaxMembers);
|
||||
|
||||
@@ -80,7 +80,6 @@ struct GuildData {
|
||||
std::optional<int> MaxVideoChannelUsers;
|
||||
std::optional<int> ApproximateMemberCount;
|
||||
std::optional<int> ApproximatePresenceCount;
|
||||
std::optional<std::vector<ChannelData>> Threads; // only with permissions to view, id only
|
||||
|
||||
// undocumented
|
||||
// std::map<std::string, Unknown> GuildHashes;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#include "httpclient.hpp"
|
||||
|
||||
//#define USE_LOCAL_PROXY
|
||||
HTTPClient::HTTPClient() {
|
||||
HTTPClient::HTTPClient(std::string api_base)
|
||||
: m_api_base(api_base) {
|
||||
m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks));
|
||||
}
|
||||
|
||||
void HTTPClient::SetBase(const std::string &url) {
|
||||
m_api_base = url;
|
||||
}
|
||||
|
||||
void HTTPClient::SetUserAgent(std::string agent) {
|
||||
m_agent = agent;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
|
||||
class HTTPClient {
|
||||
public:
|
||||
HTTPClient();
|
||||
|
||||
void SetBase(const std::string &url);
|
||||
HTTPClient(std::string api_base);
|
||||
|
||||
void SetUserAgent(std::string agent);
|
||||
void SetAuth(std::string auth);
|
||||
|
||||
@@ -10,8 +10,6 @@ void from_json(const nlohmann::json &j, GuildMember &m) {
|
||||
JS_D("deaf", m.IsDeafened);
|
||||
JS_D("mute", m.IsMuted);
|
||||
JS_O("user_id", m.UserID);
|
||||
JS_ON("avatar", m.Avatar);
|
||||
JS_O("pending", m.IsPending);
|
||||
}
|
||||
|
||||
std::vector<RoleData> GuildMember::GetSortedRoles() const {
|
||||
@@ -35,6 +33,4 @@ void GuildMember::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("nick", Nickname);
|
||||
JS_RD("joined_at", JoinedAt);
|
||||
JS_RD("premium_since", PremiumSince);
|
||||
JS_RD("avatar", Avatar);
|
||||
JS_RD("pending", IsPending);
|
||||
}
|
||||
|
||||
@@ -8,17 +8,13 @@
|
||||
|
||||
struct GuildMember {
|
||||
std::optional<UserData> User; // only reliable to access id. only opt in MESSAGE_*
|
||||
std::string Nickname;
|
||||
std::string Nickname; // null
|
||||
std::vector<Snowflake> Roles;
|
||||
std::string JoinedAt;
|
||||
std::optional<std::string> PremiumSince; // null
|
||||
bool IsDeafened;
|
||||
bool IsMuted;
|
||||
std::optional<Snowflake> UserID; // present in merged_members
|
||||
std::optional<bool> IsPending; // this uses `pending` not `is_pending`
|
||||
|
||||
// undocuemtned moment !!!1
|
||||
std::optional<std::string> Avatar;
|
||||
|
||||
std::vector<RoleData> GetSortedRoles() const;
|
||||
|
||||
|
||||
@@ -218,7 +218,6 @@ void from_json(const nlohmann::json &j, Message &m) {
|
||||
m.ReferencedMessage = nullptr;
|
||||
}
|
||||
JS_O("interaction", m.Interaction);
|
||||
JS_O("sticker_items", m.StickerItems);
|
||||
}
|
||||
|
||||
void Message::from_json_edited(const nlohmann::json &j) {
|
||||
@@ -245,7 +244,6 @@ void Message::from_json_edited(const nlohmann::json &j) {
|
||||
JS_O("flags", Flags);
|
||||
JS_O("stickers", Stickers);
|
||||
JS_O("interaction", Interaction);
|
||||
JS_O("sticker_items", StickerItems);
|
||||
}
|
||||
|
||||
void Message::SetDeleted() {
|
||||
|
||||
@@ -27,7 +27,7 @@ enum class MessageType {
|
||||
GUILD_DISCOVERY_REQUALIFIED = 15, // yep
|
||||
GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING = 16, // yep
|
||||
GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING = 17, // yep
|
||||
THREAD_CREATED = 18, // yep
|
||||
THREAD_CREATED = 18, // nope
|
||||
INLINE_REPLY = 19, // yep
|
||||
APPLICATION_COMMAND = 20, // yep
|
||||
THREAD_STARTER_MESSAGE = 21, // nope
|
||||
@@ -199,7 +199,6 @@ struct Message {
|
||||
std::optional<std::vector<StickerData>> Stickers;
|
||||
std::optional<std::shared_ptr<Message>> ReferencedMessage; // has_value && null means deleted
|
||||
std::optional<MessageInteractionData> Interaction;
|
||||
std::optional<std::vector<StickerItem>> StickerItems;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, Message &m);
|
||||
void from_json_edited(const nlohmann::json &j); // for MESSAGE_UPDATE
|
||||
|
||||
@@ -85,16 +85,10 @@ void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
|
||||
for (const auto &[key, chans] : *m.Channels)
|
||||
j["d"]["channels"][std::to_string(key)] = chans;
|
||||
}
|
||||
if (m.ShouldGetTyping)
|
||||
j["d"]["typing"] = *m.ShouldGetTyping;
|
||||
if (m.ShouldGetActivities)
|
||||
j["d"]["activities"] = *m.ShouldGetActivities;
|
||||
if (m.ShouldGetThreads)
|
||||
j["d"]["threads"] = *m.ShouldGetThreads;
|
||||
j["d"]["typing"] = m.ShouldGetTyping;
|
||||
j["d"]["activities"] = m.ShouldGetActivities;
|
||||
if (m.Members.has_value())
|
||||
j["d"]["members"] = *m.Members;
|
||||
if (m.ThreadIDs.has_value())
|
||||
j["d"]["thread_member_lists"] = *m.ThreadIDs;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||
@@ -108,7 +102,7 @@ void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||
j["d"]["status"] = "online";
|
||||
break;
|
||||
case PresenceStatus::Offline:
|
||||
j["d"]["status"] = "invisible";
|
||||
j["d"]["status"] = "offline";
|
||||
break;
|
||||
case PresenceStatus::Idle:
|
||||
j["d"]["status"] = "idle";
|
||||
@@ -458,77 +452,3 @@ void from_json(const nlohmann::json &j, RateLimitedResponse &m) {
|
||||
JS_O("message", m.Message);
|
||||
JS_D("retry_after", m.RetryAfter);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, RelationshipRemoveData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("type", m.Type);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, RelationshipAddData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("type", m.Type);
|
||||
JS_D("user", m.User);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const FriendRequestObject &m) {
|
||||
j["username"] = m.Username;
|
||||
j["discriminator"] = m.Discriminator;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const PutRelationshipObject &m) {
|
||||
JS_IF("type", m.Type);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadCreateData &m) {
|
||||
j.get_to(m.Channel);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadDeleteData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
JS_D("parent_id", m.ParentID);
|
||||
JS_D("type", m.Type);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadListSyncData &m) {
|
||||
JS_D("threads", m.Threads);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
JS_D("member_count", m.MemberCount);
|
||||
JS_O("added_members", m.AddedMembers);
|
||||
JS_O("removed_member_ids", m.RemovedMemberIDs);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m) {
|
||||
JS_D("threads", m.Threads);
|
||||
JS_D("members", m.Members);
|
||||
JS_D("has_more", m.HasMore);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m) {
|
||||
m.Member = j;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadUpdateData &m) {
|
||||
m.Thread = j;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData::UserEntry &m) {
|
||||
JS_D("user_id", m.UserID);
|
||||
JS_D("member", m.Member);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m) {
|
||||
JS_D("thread_id", m.ThreadID);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
JS_D("members", m.Members);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ModifyChannelObject &m) {
|
||||
JS_IF("archived", m.Archived);
|
||||
JS_IF("locked", m.Locked);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "ban.hpp"
|
||||
#include "auditlog.hpp"
|
||||
#include "relationship.hpp"
|
||||
#include "errors.hpp"
|
||||
|
||||
// most stuff below should just be objects that get processed and thrown away immediately
|
||||
|
||||
@@ -69,15 +68,6 @@ enum class GatewayEvent : int {
|
||||
GUILD_JOIN_REQUEST_CREATE,
|
||||
GUILD_JOIN_REQUEST_UPDATE,
|
||||
GUILD_JOIN_REQUEST_DELETE,
|
||||
RELATIONSHIP_REMOVE,
|
||||
RELATIONSHIP_ADD,
|
||||
THREAD_CREATE,
|
||||
THREAD_UPDATE,
|
||||
THREAD_DELETE,
|
||||
THREAD_LIST_SYNC,
|
||||
THREAD_MEMBER_UPDATE,
|
||||
THREAD_MEMBERS_UPDATE,
|
||||
THREAD_MEMBER_LIST_UPDATE,
|
||||
};
|
||||
|
||||
enum class GatewayCloseCode : uint16_t {
|
||||
@@ -205,12 +195,10 @@ struct GuildMemberListUpdateMessage {
|
||||
|
||||
struct LazyLoadRequestMessage {
|
||||
Snowflake GuildID;
|
||||
std::optional<bool> ShouldGetTyping;
|
||||
std::optional<bool> ShouldGetActivities;
|
||||
std::optional<bool> ShouldGetThreads;
|
||||
std::optional<std::vector<std::string>> Members; // snowflake?
|
||||
std::optional<std::map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
|
||||
std::optional<std::vector<Snowflake>> ThreadIDs;
|
||||
bool ShouldGetTyping = false;
|
||||
bool ShouldGetActivities = false;
|
||||
std::optional<std::vector<std::string>> Members; // snowflake?
|
||||
std::optional<std::unordered_map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
|
||||
|
||||
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
|
||||
};
|
||||
@@ -638,110 +626,3 @@ struct RateLimitedResponse {
|
||||
|
||||
friend void from_json(const nlohmann::json &j, RateLimitedResponse &m);
|
||||
};
|
||||
|
||||
struct RelationshipRemoveData {
|
||||
Snowflake ID;
|
||||
RelationshipType Type;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, RelationshipRemoveData &m);
|
||||
};
|
||||
|
||||
struct RelationshipAddData {
|
||||
Snowflake ID;
|
||||
// Nickname; same deal as the other comment somewhere else
|
||||
RelationshipType Type;
|
||||
UserData User;
|
||||
// std::optional<bool> ShouldNotify; // i guess if the client should send a notification. not worth caring about
|
||||
|
||||
friend void from_json(const nlohmann::json &j, RelationshipAddData &m);
|
||||
};
|
||||
|
||||
struct FriendRequestObject {
|
||||
std::string Username;
|
||||
int Discriminator;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const FriendRequestObject &m);
|
||||
};
|
||||
|
||||
struct PutRelationshipObject {
|
||||
std::optional<RelationshipType> Type;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const PutRelationshipObject &m);
|
||||
};
|
||||
|
||||
struct ThreadCreateData {
|
||||
ChannelData Channel;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadCreateData &m);
|
||||
};
|
||||
|
||||
struct ThreadDeleteData {
|
||||
Snowflake ID;
|
||||
Snowflake GuildID;
|
||||
Snowflake ParentID;
|
||||
ChannelType Type;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadDeleteData &m);
|
||||
};
|
||||
|
||||
// pretty different from docs
|
||||
struct ThreadListSyncData {
|
||||
std::vector<ChannelData> Threads;
|
||||
Snowflake GuildID;
|
||||
// std::optional<std::vector<???>> MostRecentMessages;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadListSyncData &m);
|
||||
};
|
||||
|
||||
struct ThreadMembersUpdateData {
|
||||
Snowflake ID;
|
||||
Snowflake GuildID;
|
||||
int MemberCount;
|
||||
std::optional<std::vector<ThreadMemberObject>> AddedMembers;
|
||||
std::optional<std::vector<Snowflake>> RemovedMemberIDs;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadMembersUpdateData &m);
|
||||
};
|
||||
|
||||
struct ArchivedThreadsResponseData {
|
||||
std::vector<ChannelData> Threads;
|
||||
std::vector<ThreadMemberObject> Members;
|
||||
bool HasMore;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ArchivedThreadsResponseData &m);
|
||||
};
|
||||
|
||||
struct ThreadMemberUpdateData {
|
||||
ThreadMemberObject Member;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadMemberUpdateData &m);
|
||||
};
|
||||
|
||||
struct ThreadUpdateData {
|
||||
ChannelData Thread;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadUpdateData &m);
|
||||
};
|
||||
|
||||
struct ThreadMemberListUpdateData {
|
||||
struct UserEntry {
|
||||
Snowflake UserID;
|
||||
// PresenceData Presence;
|
||||
GuildMember Member;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserEntry &m);
|
||||
};
|
||||
|
||||
Snowflake ThreadID;
|
||||
Snowflake GuildID;
|
||||
std::vector<UserEntry> Members;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ThreadMemberListUpdateData &m);
|
||||
};
|
||||
|
||||
struct ModifyChannelObject {
|
||||
std::optional<bool> Archived;
|
||||
std::optional<bool> Locked;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ModifyChannelObject &m);
|
||||
};
|
||||
|
||||
@@ -40,11 +40,10 @@ enum class Permission : uint64_t {
|
||||
MANAGE_EMOJIS = (1ULL << 30), // Allows management and editing of emojis
|
||||
USE_SLASH_COMMANDS = (1ULL << 31), // Allows members to use slash commands in text channels
|
||||
REQUEST_TO_SPEAK = (1ULL << 32), // Allows for requesting to speak in stage channels
|
||||
MANAGE_THREADS = (1ULL << 34), // Allows for deleting and archiving threads, and viewing all private threads
|
||||
USE_PUBLIC_THREADS = (1ULL << 35), // Allows for creating and participating in threads
|
||||
USE_PRIVATE_THREADS = (1ULL << 36), // Allows for creating and participating in private threads
|
||||
USE_THREADS = (1ULL << 33), // Allows for creating and participating in threads
|
||||
USE_PRIVATE_THREADS = (1ULL << 34), // Allows for creating and participating in private threads
|
||||
|
||||
ALL = 0x1FFFFFFFFFULL,
|
||||
ALL = 0x7FFFFFFFFULL,
|
||||
};
|
||||
template<>
|
||||
struct Bitwise<Permission> {
|
||||
@@ -108,7 +107,7 @@ constexpr const char *GetPermissionString(Permission perm) {
|
||||
case Permission::USE_EXTERNAL_EMOJIS:
|
||||
return "Use External Emojis";
|
||||
case Permission::VIEW_GUILD_INSIGHTS:
|
||||
return "View Server Insights";
|
||||
return "View Guild Insights";
|
||||
case Permission::CONNECT:
|
||||
return "Connect to Voice";
|
||||
case Permission::SPEAK:
|
||||
@@ -133,12 +132,6 @@ constexpr const char *GetPermissionString(Permission perm) {
|
||||
return "Manage Emojis";
|
||||
case Permission::USE_SLASH_COMMANDS:
|
||||
return "Use Slash Commands";
|
||||
case Permission::MANAGE_THREADS:
|
||||
return "Manage Threads";
|
||||
case Permission::USE_PUBLIC_THREADS:
|
||||
return "Use Public Threads";
|
||||
case Permission::USE_PRIVATE_THREADS:
|
||||
return "Use Private Threads";
|
||||
default:
|
||||
return "Unknown Permission";
|
||||
}
|
||||
@@ -187,7 +180,7 @@ constexpr const char *GetPermissionDescription(Permission perm) {
|
||||
case Permission::USE_EXTERNAL_EMOJIS:
|
||||
return "Allows members to use emoji from other servers, if they're a Discord Nitro member";
|
||||
case Permission::VIEW_GUILD_INSIGHTS:
|
||||
return "Allows members to view Server Insights, which shows data on community growth, engagement, and more.";
|
||||
return "";
|
||||
case Permission::CONNECT:
|
||||
return "Allows members to join voice channels and hear others.";
|
||||
case Permission::SPEAK:
|
||||
@@ -212,12 +205,6 @@ constexpr const char *GetPermissionDescription(Permission perm) {
|
||||
return "Allows members to add or remove custom emojis in this server.";
|
||||
case Permission::USE_SLASH_COMMANDS:
|
||||
return "Allows members to use slash commands in text channels.";
|
||||
case Permission::MANAGE_THREADS:
|
||||
return "Allows members to rename, delete, archive/unarchive, and turn on slow mode for threads.";
|
||||
case Permission::USE_PUBLIC_THREADS:
|
||||
return "Allows members to talk in threads. The \"Send Messages\" permission must be enabled for members to start new threads; if it's disabled, they can only respond to existing threads.";
|
||||
case Permission::USE_PRIVATE_THREADS:
|
||||
return "Allows members to create and chat in private threads. The \"Send Messages\" permission must be enabled for members to start new private threads; if it's disabled, they can only respond to private threads they're added to.";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
constexpr static uint64_t DiscordEpochSeconds = 1420070400;
|
||||
|
||||
const Snowflake Snowflake::Invalid = -1ULL;
|
||||
|
||||
Snowflake::Snowflake()
|
||||
: m_num(Invalid) {}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ struct Snowflake {
|
||||
return m_num;
|
||||
}
|
||||
|
||||
const static Snowflake Invalid; // makes sense to me
|
||||
const static uint64_t Invalid = -1ULL; // makes sense to me
|
||||
const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart
|
||||
|
||||
friend void from_json(const nlohmann::json &j, Snowflake &s);
|
||||
|
||||
@@ -30,23 +30,3 @@ std::string StickerData::GetURL() const {
|
||||
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + "/" + *AssetHash + ".json";
|
||||
return "";
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const StickerItem &m) {
|
||||
j["id"] = m.ID;
|
||||
j["name"] = m.Name;
|
||||
j["format_type"] = m.FormatType;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, StickerItem &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("name", m.Name);
|
||||
JS_D("format_type", m.FormatType);
|
||||
}
|
||||
|
||||
std::string StickerItem::GetURL() const {
|
||||
if (FormatType == StickerFormatType::PNG || FormatType == StickerFormatType::APNG)
|
||||
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".png?size=256";
|
||||
else if (FormatType == StickerFormatType::LOTTIE)
|
||||
return "https://media.discordapp.net/stickers/" + std::to_string(ID) + ".json";
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -27,14 +27,3 @@ struct StickerData {
|
||||
|
||||
std::string GetURL() const;
|
||||
};
|
||||
|
||||
struct StickerItem {
|
||||
StickerFormatType FormatType;
|
||||
Snowflake ID;
|
||||
std::string Name;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const StickerItem &m);
|
||||
friend void from_json(const nlohmann::json &j, StickerItem &m);
|
||||
|
||||
std::string GetURL() const;
|
||||
};
|
||||
|
||||
@@ -105,16 +105,6 @@ void Store::SetChannel(Snowflake id, const ChannelData &chan) {
|
||||
Bind(m_set_chan_stmt, 17, chan.ParentID);
|
||||
Bind(m_set_chan_stmt, 18, chan.LastPinTimestamp);
|
||||
|
||||
if (chan.ThreadMetadata.has_value()) {
|
||||
Bind(m_set_chan_stmt, 19, chan.ThreadMetadata->IsArchived);
|
||||
Bind(m_set_chan_stmt, 20, chan.ThreadMetadata->AutoArchiveDuration);
|
||||
Bind(m_set_chan_stmt, 21, chan.ThreadMetadata->ArchiveTimestamp);
|
||||
} else {
|
||||
Bind(m_set_chan_stmt, 19, nullptr);
|
||||
Bind(m_set_chan_stmt, 20, nullptr);
|
||||
Bind(m_set_chan_stmt, 21, nullptr);
|
||||
}
|
||||
|
||||
if (!RunInsert(m_set_chan_stmt))
|
||||
fprintf(stderr, "channel insert failed: %s\n", sqlite3_errstr(m_db_err));
|
||||
|
||||
@@ -204,12 +194,6 @@ void Store::SetGuild(Snowflake id, const GuildData &guild) {
|
||||
Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount);
|
||||
Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount);
|
||||
Bind(m_set_guild_stmt, 39, guild.IsLazy);
|
||||
if (guild.Threads.has_value()) {
|
||||
snowflakes.clear();
|
||||
for (const auto &x : *guild.Threads) snowflakes.push_back(x.ID);
|
||||
Bind(m_set_guild_stmt, 40, nlohmann::json(snowflakes).dump());
|
||||
} else
|
||||
Bind(m_set_guild_stmt, 40, "[]"s);
|
||||
|
||||
if (!RunInsert(m_set_guild_stmt))
|
||||
fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err));
|
||||
@@ -226,8 +210,6 @@ void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMem
|
||||
Bind(m_set_member_stmt, 6, data.PremiumSince);
|
||||
Bind(m_set_member_stmt, 7, data.IsDeafened);
|
||||
Bind(m_set_member_stmt, 8, data.IsMuted);
|
||||
Bind(m_set_member_stmt, 9, data.Avatar);
|
||||
Bind(m_set_member_stmt, 10, data.IsPending);
|
||||
|
||||
if (!RunInsert(m_set_member_stmt))
|
||||
fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err));
|
||||
@@ -280,12 +262,6 @@ void Store::SetMessage(Snowflake id, const Message &message) {
|
||||
Bind(m_set_msg_stmt, 23, message.IsPending);
|
||||
Bind(m_set_msg_stmt, 24, message.Nonce); // sorry
|
||||
|
||||
if (message.StickerItems.has_value()) {
|
||||
std::string tmp = nlohmann::json(*message.StickerItems).dump();
|
||||
Bind(m_set_msg_stmt, 25, tmp);
|
||||
} else
|
||||
Bind(m_set_msg_stmt, 25, nullptr);
|
||||
|
||||
if (!RunInsert(m_set_msg_stmt))
|
||||
fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err));
|
||||
|
||||
@@ -338,86 +314,6 @@ void Store::SetUser(Snowflake id, const UserData &user) {
|
||||
}
|
||||
}
|
||||
|
||||
Message Store::GetMessageBound(sqlite3_stmt *stmt) const {
|
||||
Message ret;
|
||||
Get(stmt, 0, ret.ID);
|
||||
Get(stmt, 1, ret.ChannelID);
|
||||
Get(stmt, 2, ret.GuildID);
|
||||
Get(stmt, 3, ret.Author.ID); // yike
|
||||
Get(stmt, 4, ret.Content);
|
||||
Get(stmt, 5, ret.Timestamp);
|
||||
Get(stmt, 6, ret.EditedTimestamp);
|
||||
Get(stmt, 7, ret.IsTTS);
|
||||
Get(stmt, 8, ret.DoesMentionEveryone);
|
||||
std::string tmps;
|
||||
Get(stmt, 9, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Mentions);
|
||||
Get(stmt, 10, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Attachments);
|
||||
Get(stmt, 11, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Embeds);
|
||||
Get(stmt, 12, ret.IsPinned);
|
||||
Get(stmt, 13, ret.WebhookID);
|
||||
uint64_t tmpi;
|
||||
Get(stmt, 14, tmpi);
|
||||
ret.Type = static_cast<MessageType>(tmpi);
|
||||
|
||||
Get(stmt, 15, tmps);
|
||||
if (tmps != "")
|
||||
ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();
|
||||
|
||||
Get(stmt, 16, tmps);
|
||||
if (tmps != "")
|
||||
ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();
|
||||
|
||||
Get(stmt, 17, tmpi);
|
||||
ret.Flags = static_cast<MessageFlags>(tmpi);
|
||||
|
||||
Get(stmt, 18, tmps);
|
||||
if (tmps != "")
|
||||
ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();
|
||||
|
||||
Get(stmt, 19, tmps);
|
||||
if (tmps != "")
|
||||
ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
|
||||
|
||||
bool tmpb = false;
|
||||
Get(stmt, 20, tmpb);
|
||||
if (tmpb) ret.SetDeleted();
|
||||
|
||||
Get(stmt, 21, tmpb);
|
||||
if (tmpb) ret.SetEdited();
|
||||
|
||||
Get(stmt, 22, ret.IsPending);
|
||||
Get(stmt, 23, ret.Nonce);
|
||||
|
||||
Get(stmt, 24, tmps);
|
||||
if (tmps != "")
|
||||
ret.StickerItems = nlohmann::json::parse(tmps).get<std::vector<StickerItem>>();
|
||||
|
||||
// interaction data from join
|
||||
|
||||
if (!IsNull(stmt, 25)) {
|
||||
auto &interaction = ret.Interaction.emplace();
|
||||
Get(stmt, 25, interaction.ID);
|
||||
Get(stmt, 26, interaction.Name);
|
||||
Get(stmt, 27, interaction.Type);
|
||||
Get(stmt, 28, interaction.User.ID);
|
||||
}
|
||||
|
||||
Reset(stmt);
|
||||
|
||||
if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
|
||||
auto ref = GetMessage(*ret.MessageReference->MessageID);
|
||||
if (ref.has_value())
|
||||
ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
|
||||
else
|
||||
ret.ReferencedMessage = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
|
||||
Bind(m_set_msg_interaction_stmt, 1, message_id);
|
||||
Bind(m_set_msg_interaction_stmt, 2, interaction.ID);
|
||||
@@ -465,69 +361,6 @@ std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
|
||||
auto ids = GetChannelMessageIDs(id);
|
||||
std::vector<Message> ret;
|
||||
for (auto it = ids.cend() - std::min(ids.size(), num); it != ids.cend(); it++)
|
||||
ret.push_back(*GetMessage(*it));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Snowflake> Store::GetChannelMessageIDs(Snowflake id) const {
|
||||
std::vector<Snowflake> ret;
|
||||
Bind(m_get_msg_ids_stmt, 1, id);
|
||||
|
||||
while (FetchOne(m_get_msg_ids_stmt)) {
|
||||
Snowflake x;
|
||||
Get(m_get_msg_ids_stmt, 0, x);
|
||||
ret.push_back(x);
|
||||
}
|
||||
|
||||
Reset(m_get_msg_ids_stmt);
|
||||
|
||||
if (m_db_err != SQLITE_DONE)
|
||||
fprintf(stderr, "error while fetching ids: %s\n", sqlite3_errstr(m_db_err));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<Message> Store::GetPinnedMessages(Snowflake channel_id) const {
|
||||
std::vector<Message> ret;
|
||||
|
||||
Bind(m_get_pins_stmt, 1, channel_id);
|
||||
while (FetchOne(m_get_pins_stmt)) {
|
||||
Snowflake x;
|
||||
Get(m_get_pins_stmt, 0, x);
|
||||
auto msg = GetMessage(x);
|
||||
if (msg.has_value())
|
||||
ret.push_back(*msg);
|
||||
}
|
||||
|
||||
Reset(m_get_pins_stmt);
|
||||
|
||||
if (m_db_err != SQLITE_DONE)
|
||||
fprintf(stderr, "error while fetching pins: %s\n", sqlite3_errstr(m_db_err));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
|
||||
std::vector<ChannelData> ret;
|
||||
|
||||
Bind(m_get_threads_stmt, 1, channel_id);
|
||||
while (FetchOne(m_get_threads_stmt)) {
|
||||
Snowflake x;
|
||||
Get(m_get_threads_stmt, 0, x);
|
||||
auto chan = GetChannel(x);
|
||||
if (chan.has_value())
|
||||
ret.push_back(*chan);
|
||||
}
|
||||
|
||||
Reset(m_get_threads_stmt);
|
||||
|
||||
if (m_db_err != SQLITE_DONE)
|
||||
fprintf(stderr, "error while fetching threads: %s\n", sqlite3_errstr(m_db_err));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
|
||||
Bind(m_get_chan_stmt, 1, id);
|
||||
if (!FetchOne(m_get_chan_stmt)) {
|
||||
@@ -562,12 +395,6 @@ std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
|
||||
Get(m_get_chan_stmt, 15, ret.ApplicationID);
|
||||
Get(m_get_chan_stmt, 16, ret.ParentID);
|
||||
Get(m_get_chan_stmt, 17, ret.LastPinTimestamp);
|
||||
if (!IsNull(m_get_chan_stmt, 18)) {
|
||||
ret.ThreadMetadata.emplace();
|
||||
Get(m_get_chan_stmt, 18, ret.ThreadMetadata->IsArchived);
|
||||
Get(m_get_chan_stmt, 19, ret.ThreadMetadata->AutoArchiveDuration);
|
||||
Get(m_get_chan_stmt, 20, ret.ThreadMetadata->ArchiveTimestamp);
|
||||
}
|
||||
|
||||
Reset(m_get_chan_stmt);
|
||||
|
||||
@@ -667,10 +494,6 @@ std::optional<GuildData> Store::GetGuild(Snowflake id) const {
|
||||
Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount);
|
||||
Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount);
|
||||
Get(m_get_guild_stmt, 38, ret.IsLazy);
|
||||
Get(m_get_guild_stmt, 39, tmp);
|
||||
ret.Threads.emplace();
|
||||
for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
|
||||
ret.Threads->emplace_back().ID = id;
|
||||
|
||||
Reset(m_get_guild_stmt);
|
||||
|
||||
@@ -697,8 +520,6 @@ std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake u
|
||||
Get(m_get_member_stmt, 5, ret.PremiumSince);
|
||||
Get(m_get_member_stmt, 6, ret.IsDeafened);
|
||||
Get(m_get_member_stmt, 7, ret.IsMuted);
|
||||
Get(m_get_member_stmt, 8, ret.Avatar);
|
||||
Get(m_get_member_stmt, 9, ret.IsPending);
|
||||
|
||||
Reset(m_get_member_stmt);
|
||||
|
||||
@@ -714,7 +535,77 @@ std::optional<Message> Store::GetMessage(Snowflake id) const {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto ret = GetMessageBound(m_get_msg_stmt);
|
||||
Message ret;
|
||||
ret.ID = id;
|
||||
Get(m_get_msg_stmt, 1, ret.ChannelID);
|
||||
Get(m_get_msg_stmt, 2, ret.GuildID);
|
||||
Get(m_get_msg_stmt, 3, ret.Author.ID); // yike
|
||||
Get(m_get_msg_stmt, 4, ret.Content);
|
||||
Get(m_get_msg_stmt, 5, ret.Timestamp);
|
||||
Get(m_get_msg_stmt, 6, ret.EditedTimestamp);
|
||||
Get(m_get_msg_stmt, 7, ret.IsTTS);
|
||||
Get(m_get_msg_stmt, 8, ret.DoesMentionEveryone);
|
||||
std::string tmps;
|
||||
Get(m_get_msg_stmt, 9, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Mentions);
|
||||
Get(m_get_msg_stmt, 10, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Attachments);
|
||||
Get(m_get_msg_stmt, 11, tmps);
|
||||
nlohmann::json::parse(tmps).get_to(ret.Embeds);
|
||||
Get(m_get_msg_stmt, 12, ret.IsPinned);
|
||||
Get(m_get_msg_stmt, 13, ret.WebhookID);
|
||||
uint64_t tmpi;
|
||||
Get(m_get_msg_stmt, 14, tmpi);
|
||||
ret.Type = static_cast<MessageType>(tmpi);
|
||||
|
||||
Get(m_get_msg_stmt, 15, tmps);
|
||||
if (tmps != "")
|
||||
ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();
|
||||
|
||||
Get(m_get_msg_stmt, 16, tmps);
|
||||
if (tmps != "")
|
||||
ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();
|
||||
|
||||
Get(m_get_msg_stmt, 17, tmpi);
|
||||
ret.Flags = static_cast<MessageFlags>(tmpi);
|
||||
|
||||
Get(m_get_msg_stmt, 18, tmps);
|
||||
if (tmps != "")
|
||||
ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();
|
||||
|
||||
Get(m_get_msg_stmt, 19, tmps);
|
||||
if (tmps != "")
|
||||
ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
|
||||
|
||||
bool tmpb = false;
|
||||
Get(m_get_msg_stmt, 20, tmpb);
|
||||
if (tmpb) ret.SetDeleted();
|
||||
|
||||
Get(m_get_msg_stmt, 21, tmpb);
|
||||
if (tmpb) ret.SetEdited();
|
||||
|
||||
Get(m_get_msg_stmt, 22, ret.IsPending);
|
||||
Get(m_get_msg_stmt, 23, ret.Nonce);
|
||||
|
||||
// interaction data from join
|
||||
|
||||
if (!IsNull(m_get_msg_stmt, 24)) {
|
||||
auto &interaction = ret.Interaction.emplace();
|
||||
Get(m_get_msg_stmt, 24, interaction.ID);
|
||||
Get(m_get_msg_stmt, 25, interaction.Name);
|
||||
Get(m_get_msg_stmt, 26, interaction.Type);
|
||||
Get(m_get_msg_stmt, 27, interaction.User.ID);
|
||||
}
|
||||
|
||||
Reset(m_get_msg_stmt);
|
||||
|
||||
if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
|
||||
auto ref = GetMessage(*ret.MessageReference->MessageID);
|
||||
if (ref.has_value())
|
||||
ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
|
||||
else
|
||||
ret.ReferencedMessage = nullptr;
|
||||
}
|
||||
|
||||
return std::optional<Message>(std::move(ret));
|
||||
}
|
||||
@@ -805,12 +696,6 @@ void Store::ClearGuild(Snowflake id) {
|
||||
|
||||
void Store::ClearChannel(Snowflake id) {
|
||||
m_channels.erase(id);
|
||||
Bind(m_clear_chan_stmt, 1, id);
|
||||
|
||||
if ((m_db_err = sqlite3_step(m_clear_chan_stmt)) != SQLITE_DONE)
|
||||
printf("clearing channel failed: %s\n", sqlite3_errstr(m_db_err));
|
||||
|
||||
Reset(m_clear_chan_stmt);
|
||||
}
|
||||
|
||||
void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
|
||||
@@ -844,7 +729,7 @@ void Store::EndTransaction() {
|
||||
}
|
||||
|
||||
bool Store::CreateTables() {
|
||||
const char *create_users = R"(
|
||||
constexpr const char *create_users = R"(
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
@@ -862,7 +747,7 @@ bool Store::CreateTables() {
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_permissions = R"(
|
||||
constexpr const char *create_permissions = R"(
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER NOT NULL,
|
||||
channel_id INTEGER NOT NULL,
|
||||
@@ -873,7 +758,7 @@ bool Store::CreateTables() {
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_messages = R"(
|
||||
constexpr const char *create_messages = R"(
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY,
|
||||
channel_id INTEGER NOT NULL,
|
||||
@@ -898,12 +783,11 @@ bool Store::CreateTables() {
|
||||
deleted BOOL, /* extra */
|
||||
edited BOOL, /* extra */
|
||||
pending BOOL, /* extra */
|
||||
nonce TEXT,
|
||||
sticker_items TEXT /* json */
|
||||
nonce TEXT
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_roles = R"(
|
||||
constexpr const char *create_roles = R"(
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
@@ -916,7 +800,7 @@ bool Store::CreateTables() {
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_emojis = R"(
|
||||
constexpr const char *create_emojis = R"(
|
||||
CREATE TABLE IF NOT EXISTS emojis (
|
||||
id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
|
||||
name TEXT NOT NULL, /*same as id*/
|
||||
@@ -929,7 +813,7 @@ bool Store::CreateTables() {
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_members = R"(
|
||||
constexpr const char *create_members = R"(
|
||||
CREATE TABLE IF NOT EXISTS members (
|
||||
user_id INTEGER NOT NULL,
|
||||
guild_id INTEGER NOT NULL,
|
||||
@@ -939,13 +823,11 @@ bool Store::CreateTables() {
|
||||
premium_since TEXT,
|
||||
deaf BOOL NOT NULL,
|
||||
mute BOOL NOT NULL,
|
||||
avatar TEXT,
|
||||
pending BOOL,
|
||||
PRIMARY KEY(user_id, guild_id)
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_guilds = R"(
|
||||
constexpr const char *create_guilds = R"(
|
||||
CREATE TABLE IF NOT EXISTS guilds (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
@@ -985,12 +867,11 @@ bool Store::CreateTables() {
|
||||
max_video_users INTEGER,
|
||||
approx_members INTEGER,
|
||||
approx_presences INTEGER,
|
||||
lazy BOOL,
|
||||
threads TEXT NOT NULL /* json */
|
||||
lazy BOOL
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_channels = R"(
|
||||
constexpr const char *create_channels = R"(
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
id INTEGER PRIMARY KEY,
|
||||
type INTEGER NOT NULL,
|
||||
@@ -1009,14 +890,11 @@ bool Store::CreateTables() {
|
||||
owner_id INTEGER,
|
||||
application_id INTEGER,
|
||||
parent_id INTEGER,
|
||||
last_pin_timestamp TEXT,
|
||||
archived BOOL, /* threads */
|
||||
auto_archive INTEGER, /* threads */
|
||||
archived_ts TEXT /* threads */
|
||||
last_pin_timestamp TEXT
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_bans = R"(
|
||||
constexpr const char *create_bans = R"(
|
||||
CREATE TABLE IF NOT EXISTS bans (
|
||||
guild_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
@@ -1025,7 +903,7 @@ bool Store::CreateTables() {
|
||||
)
|
||||
)";
|
||||
|
||||
const char *create_interactions = R"(
|
||||
constexpr const char *create_interactions = R"(
|
||||
CREATE TABLE IF NOT EXISTS message_interactions (
|
||||
message_id INTEGER NOT NULL,
|
||||
interaction_id INTEGER NOT NULL,
|
||||
@@ -1100,33 +978,33 @@ bool Store::CreateTables() {
|
||||
}
|
||||
|
||||
bool Store::CreateStatements() {
|
||||
const char *set_user = R"(
|
||||
constexpr const char *set_user = R"(
|
||||
REPLACE INTO users VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_user = R"(
|
||||
constexpr const char *get_user = R"(
|
||||
SELECT * FROM users WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_perm = R"(
|
||||
constexpr const char *set_perm = R"(
|
||||
REPLACE INTO permissions VALUES (
|
||||
?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_perm = R"(
|
||||
constexpr const char *get_perm = R"(
|
||||
SELECT * FROM permissions WHERE id = ? AND channel_id = ?
|
||||
)";
|
||||
|
||||
const char *set_msg = R"(
|
||||
constexpr const char *set_msg = R"(
|
||||
REPLACE INTO messages VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_msg = R"(
|
||||
constexpr const char *get_msg = R"(
|
||||
SELECT messages.*,
|
||||
message_interactions.interaction_id as interaction_id,
|
||||
message_interactions.name as interaction_name,
|
||||
@@ -1139,105 +1017,80 @@ bool Store::CreateStatements() {
|
||||
WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_role = R"(
|
||||
constexpr const char *set_role = R"(
|
||||
REPLACE INTO roles VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_role = R"(
|
||||
constexpr const char *get_role = R"(
|
||||
SELECT * FROM roles WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_emoji = R"(
|
||||
constexpr const char *set_emoji = R"(
|
||||
REPLACE INTO emojis VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_emoji = R"(
|
||||
constexpr const char *get_emoji = R"(
|
||||
SELECT * FROM emojis WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_member = R"(
|
||||
constexpr const char *set_member = R"(
|
||||
REPLACE INTO members VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_member = R"(
|
||||
constexpr const char *get_member = R"(
|
||||
SELECT * FROM members WHERE user_id = ? AND guild_id = ?
|
||||
)";
|
||||
|
||||
const char *set_guild = R"(
|
||||
constexpr const char *set_guild = R"(
|
||||
REPLACE INTO guilds VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_guild = R"(
|
||||
constexpr const char *get_guild = R"(
|
||||
SELECT * FROM guilds WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_chan = R"(
|
||||
constexpr const char *set_chan = R"(
|
||||
REPLACE INTO channels VALUES (
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_chan = R"(
|
||||
constexpr const char *get_chan = R"(
|
||||
SELECT * FROM channels WHERE id = ?
|
||||
)";
|
||||
|
||||
const char *set_ban = R"(
|
||||
constexpr const char *set_ban = R"(
|
||||
REPLACE INTO bans VALUES (
|
||||
?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_ban = R"(
|
||||
constexpr const char *get_ban = R"(
|
||||
SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
|
||||
)";
|
||||
|
||||
const char *clear_ban = R"(
|
||||
constexpr const char *clear_ban = R"(
|
||||
DELETE FROM bans WHERE guild_id = ? AND user_id = ?
|
||||
)";
|
||||
|
||||
const char *get_bans = R"(
|
||||
constexpr const char *get_bans = R"(
|
||||
SELECT * FROM bans WHERE guild_id = ?
|
||||
)";
|
||||
|
||||
const char *set_interaction = R"(
|
||||
constexpr const char *set_interaction = R"(
|
||||
REPLACE INTO message_interactions VALUES (
|
||||
?, ?, ?, ?, ?
|
||||
)
|
||||
)";
|
||||
|
||||
const char *get_last_msgs = R"(
|
||||
SELECT * FROM (
|
||||
SELECT * FROM messages
|
||||
WHERE channel_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
) T1 ORDER BY id ASC
|
||||
)";
|
||||
|
||||
const char *get_msg_ids = R"(
|
||||
SELECT id FROM messages WHERE channel_id = ? AND pending = 0 ORDER BY id ASC
|
||||
)";
|
||||
|
||||
const char *get_pins = R"(
|
||||
SELECT id FROM messages WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
|
||||
)";
|
||||
|
||||
const char *get_threads = R"(
|
||||
SELECT id FROM channels WHERE parent_id = ? AND (type = 10 OR type = 11 OR type = 12) AND archived = FALSE
|
||||
)";
|
||||
|
||||
const char *clear_chan = R"(
|
||||
DELETE FROM channels WHERE id = ?
|
||||
)";
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
@@ -1364,36 +1217,6 @@ bool Store::CreateStatements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, get_last_msgs, -1, &m_get_last_msgs_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare get last messages statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, get_msg_ids, -1, &m_get_msg_ids_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare get msg ids statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, get_pins, -1, &m_get_pins_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare get pins statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, get_threads, -1, &m_get_threads_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare get threads statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_db_err = sqlite3_prepare_v2(m_db, clear_chan, -1, &m_clear_chan_stmt, nullptr);
|
||||
if (m_db_err != SQLITE_OK) {
|
||||
fprintf(stderr, "failed to prepare clear channel statement: %s\n", sqlite3_errstr(m_db_err));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1419,11 +1242,6 @@ void Store::Cleanup() {
|
||||
sqlite3_finalize(m_clear_ban_stmt);
|
||||
sqlite3_finalize(m_get_bans_stmt);
|
||||
sqlite3_finalize(m_set_msg_interaction_stmt);
|
||||
sqlite3_finalize(m_get_last_msgs_stmt);
|
||||
sqlite3_finalize(m_get_msg_ids_stmt);
|
||||
sqlite3_finalize(m_get_pins_stmt);
|
||||
sqlite3_finalize(m_get_threads_stmt);
|
||||
sqlite3_finalize(m_clear_chan_stmt);
|
||||
}
|
||||
|
||||
void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
|
||||
|
||||
@@ -41,11 +41,6 @@ public:
|
||||
std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
|
||||
std::vector<BanData> GetBans(Snowflake guild_id) const;
|
||||
|
||||
std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
|
||||
std::vector<Snowflake> GetChannelMessageIDs(Snowflake id) const;
|
||||
std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
|
||||
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
|
||||
|
||||
void ClearGuild(Snowflake id);
|
||||
void ClearChannel(Snowflake id);
|
||||
void ClearBan(Snowflake guild_id, Snowflake user_id);
|
||||
@@ -68,8 +63,6 @@ public:
|
||||
void EndTransaction();
|
||||
|
||||
private:
|
||||
Message GetMessageBound(sqlite3_stmt *stmt) const;
|
||||
|
||||
void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
|
||||
|
||||
std::unordered_set<Snowflake> m_channels;
|
||||
@@ -133,11 +126,6 @@ private:
|
||||
mutable sqlite3_stmt *m_clear_ban_stmt;
|
||||
mutable sqlite3_stmt *m_get_bans_stmt;
|
||||
mutable sqlite3_stmt *m_set_msg_interaction_stmt;
|
||||
mutable sqlite3_stmt *m_get_last_msgs_stmt;
|
||||
mutable sqlite3_stmt *m_get_msg_ids_stmt;
|
||||
mutable sqlite3_stmt *m_get_pins_stmt;
|
||||
mutable sqlite3_stmt *m_get_threads_stmt;
|
||||
mutable sqlite3_stmt *m_clear_chan_stmt;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
@@ -13,33 +13,11 @@ bool UserData::HasAnimatedAvatar() const {
|
||||
return Avatar.size() > 0 && Avatar[0] == 'a' && Avatar[1] == '_';
|
||||
}
|
||||
|
||||
std::string UserData::GetAvatarURL(Snowflake guild_id, std::string ext, std::string size) const {
|
||||
const auto member = Abaddon::Get().GetDiscordClient().GetMember(ID, guild_id);
|
||||
if (member.has_value() && member->Avatar.has_value())
|
||||
return "https://cdn.discordapp.com/guilds/" +
|
||||
std::to_string(guild_id) + "/users/" + std::to_string(ID) +
|
||||
"/avatars/" + *member->Avatar + "." +
|
||||
ext + "?" + "size=" + size;
|
||||
else
|
||||
return GetAvatarURL(ext, size);
|
||||
}
|
||||
|
||||
std::string UserData::GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext, std::string size) const {
|
||||
if (guild_id.has_value())
|
||||
return GetAvatarURL(*guild_id, ext, size);
|
||||
else
|
||||
return GetAvatarURL(ext, size);
|
||||
}
|
||||
|
||||
std::string UserData::GetAvatarURL(std::string ext, std::string size) const {
|
||||
if (HasAvatar())
|
||||
return "https://cdn.discordapp.com/avatars/" + std::to_string(ID) + "/" + Avatar + "." + ext + "?size=" + size;
|
||||
else
|
||||
return GetDefaultAvatarURL();
|
||||
}
|
||||
|
||||
std::string UserData::GetDefaultAvatarURL() const {
|
||||
return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn
|
||||
return "https://cdn.discordapp.com/embed/avatars/" + std::to_string(std::stoul(Discriminator) % 5) + ".png"; // size isn't respected by the cdn
|
||||
}
|
||||
|
||||
Snowflake UserData::GetHoistedRole(Snowflake guild_id, bool with_color) const {
|
||||
@@ -80,8 +58,6 @@ void from_json(const nlohmann::json &j, UserData &m) {
|
||||
JS_O("mobile", m.IsMobile);
|
||||
JS_ON("nsfw_allowed", m.IsNSFWAllowed);
|
||||
JS_ON("phone", m.Phone);
|
||||
JS_ON("bio", m.Bio);
|
||||
JS_ON("banner", m.BannerHash);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UserData &m) {
|
||||
@@ -154,8 +130,6 @@ const char *UserData::GetFlagName(uint64_t flag) {
|
||||
return "verifiedbot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
return "earlyverifiedbotdeveloper";
|
||||
case CertifiedModerator:
|
||||
return "certifiedmoderator";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
@@ -189,8 +163,6 @@ const char *UserData::GetFlagReadableName(uint64_t flag) {
|
||||
return "Verified Bot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
return "Early Verified Bot Developer";
|
||||
case CertifiedModerator:
|
||||
return "Discord Certified Moderator";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -24,10 +24,8 @@ struct UserData {
|
||||
BugHunterLevel2 = 1 << 14,
|
||||
VerifiedBot = 1 << 16,
|
||||
EarlyVerifiedBotDeveloper = 1 << 17,
|
||||
CertifiedModerator = 1 << 18,
|
||||
|
||||
MaxFlag_PlusOne,
|
||||
MaxFlag = MaxFlag_PlusOne - 1,
|
||||
MaxFlag = EarlyVerifiedBotDeveloper,
|
||||
};
|
||||
|
||||
static const char *GetFlagName(uint64_t flag);
|
||||
@@ -52,9 +50,6 @@ struct UserData {
|
||||
std::optional<bool> IsMobile;
|
||||
std::optional<bool> IsNSFWAllowed; // null
|
||||
std::optional<std::string> Phone; // null?
|
||||
// for now (unserialized)
|
||||
std::optional<std::string> BannerHash; // null
|
||||
std::optional<std::string> Bio; // null
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserData &m);
|
||||
friend void to_json(nlohmann::json &j, const UserData &m);
|
||||
@@ -63,10 +58,7 @@ struct UserData {
|
||||
bool IsDeleted() const;
|
||||
bool HasAvatar() const;
|
||||
bool HasAnimatedAvatar() const;
|
||||
std::string GetAvatarURL(Snowflake guild_id, std::string ext = "png", std::string size = "32") const;
|
||||
std::string GetAvatarURL(const std::optional<Snowflake> &guild_id, std::string ext = "png", std::string size = "32") const;
|
||||
std::string GetAvatarURL(std::string ext = "png", std::string size = "32") const;
|
||||
std::string GetDefaultAvatarURL() const;
|
||||
Snowflake GetHoistedRole(Snowflake guild_id, bool with_color = false) const;
|
||||
std::string GetMention() const;
|
||||
std::string GetEscapedName() const;
|
||||
|
||||
@@ -17,13 +17,8 @@ Cache::Cache() {
|
||||
Cache::~Cache() {
|
||||
m_worker.stop();
|
||||
|
||||
for (auto &future : m_futures) {
|
||||
if (future.valid()) {
|
||||
try { // dont care about stored exceptions
|
||||
future.get();
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
for (auto &future : m_futures)
|
||||
if (future.valid()) future.get();
|
||||
|
||||
std::error_code err;
|
||||
if (!std::filesystem::remove_all(m_tmp_path, err))
|
||||
@@ -130,10 +125,6 @@ void FileCacheWorkerThread::stop() {
|
||||
}
|
||||
|
||||
void FileCacheWorkerThread::loop() {
|
||||
timeval timeout;
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
while (!m_stop) {
|
||||
if (m_handles.size() == 0) {
|
||||
std::unique_lock<std::mutex> lock(m_queue_mutex);
|
||||
@@ -142,8 +133,7 @@ void FileCacheWorkerThread::loop() {
|
||||
m_cv.wait(lock);
|
||||
}
|
||||
|
||||
static const auto concurrency = Abaddon::Get().GetSettings().GetCacheHTTPConcurrency();
|
||||
if (m_handles.size() < concurrency) {
|
||||
if (m_handles.size() < Abaddon::Get().GetSettings().GetCacheHTTPConcurrency()) {
|
||||
std::optional<QueueEntry> entry;
|
||||
m_queue_mutex.lock();
|
||||
if (m_queue.size() > 0) {
|
||||
@@ -181,20 +171,8 @@ void FileCacheWorkerThread::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
fd_set fdread;
|
||||
fd_set fdwrite;
|
||||
fd_set fdexcep;
|
||||
int maxfd = -1;
|
||||
FD_ZERO(&fdread);
|
||||
FD_ZERO(&fdwrite);
|
||||
FD_ZERO(&fdexcep);
|
||||
curl_multi_fdset(m_multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd);
|
||||
if (maxfd == -1) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
} else {
|
||||
select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout);
|
||||
}
|
||||
|
||||
//int fds;
|
||||
//curl_multi_wait(m_multi_handle, nullptr, 0, 10, &fds);
|
||||
curl_multi_perform(m_multi_handle, &m_running_handles);
|
||||
|
||||
int num_msgs;
|
||||
|
||||
Binary file not shown.
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Use the Autohinter -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="autohint" mode="append"><bool>true</bool></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="hintstyle" mode="append"><const>hintfull</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="hintstyle" mode="append"><const>hintmedium</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="hintstyle" mode="append"><const>hintnone</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="hintstyle" mode="append"><const>hintslight</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Disable sub-pixel rendering -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="rgba" mode="append"><const>none</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
|
||||
<!--
|
||||
If font is bitmap, calculate scale factor.
|
||||
Note that color bitmap fonts have scalable=true, while
|
||||
non-color ones have scalable=false. Both groups have outline=false.
|
||||
-->
|
||||
<match target="font">
|
||||
<test name="outline" compare="eq">
|
||||
<bool>false</bool>
|
||||
</test>
|
||||
<edit name="pixelsizefixupfactor" mode="assign">
|
||||
<double>0.15</double>
|
||||
</edit>
|
||||
</match>
|
||||
<!--
|
||||
For non-scalable bitmap fonts (ie. non-color), skip
|
||||
minor scaling if hinting is enabled.
|
||||
-->
|
||||
<match target="font">
|
||||
<test name="outline" compare="eq">
|
||||
<bool>false</bool>
|
||||
</test>
|
||||
<test name="scalable" compare="eq">
|
||||
<bool>false</bool>
|
||||
</test>
|
||||
<test name="hinting" compare="eq">
|
||||
<bool>true</bool>
|
||||
</test>
|
||||
<edit name="scalingnotneeded" mode="assign">
|
||||
<and>
|
||||
<less>
|
||||
<name>pixelsizefixupfactor</name>
|
||||
<double>1.2</double>
|
||||
</less>
|
||||
<more>
|
||||
<name>pixelsizefixupfactor</name>
|
||||
<double>0.8</double>
|
||||
</more>
|
||||
</and>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="scalingnotneeded" compare="eq">
|
||||
<bool>true</bool>
|
||||
</test>
|
||||
<edit name="pixelsizefixupfactor" mode="assign">
|
||||
<double>1.0</double>
|
||||
</edit>
|
||||
</match>
|
||||
<!--
|
||||
If we *are* going to scale, go ahead and do it.
|
||||
-->
|
||||
<match target="font">
|
||||
<test name="outline" compare="eq">
|
||||
<bool>false</bool>
|
||||
</test>
|
||||
<test name="pixelsizefixupfactor" compare="not_eq">
|
||||
<double>1.0</double>
|
||||
</test>
|
||||
<edit name="matrix" mode="assign">
|
||||
<times>
|
||||
<name>matrix</name>
|
||||
<matrix>
|
||||
<name>pixelsizefixupfactor</name> <double>0</double>
|
||||
<double>0</double> <name>pixelsizefixupfactor</name>
|
||||
</matrix>
|
||||
</times>
|
||||
</edit>
|
||||
<edit name="size" mode="assign">
|
||||
<divide>
|
||||
<name>size</name>
|
||||
<name>pixelsizefixupfactor</name>
|
||||
</divide>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Enable sub-pixel rendering -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="rgba" mode="append"><const>bgr</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Enable sub-pixel rendering -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="rgba" mode="append"><const>rgb</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Enable sub-pixel rendering -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="rgba" mode="append"><const>vbgr</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Enable sub-pixel rendering -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="rgba" mode="append"><const>vrgb</const></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Disable hinting -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit name="hinting" mode="append"><bool>false</bool></edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Use lcddefault as default for LCD filter -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit mode="append" name="lcdfilter">
|
||||
<const>lcddefault</const>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Use lcdlegacy as default for LCD filter -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit mode="append" name="lcdfilter">
|
||||
<const>lcdlegacy</const>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Use lcdlight as default for LCD filter -->
|
||||
<match target="pattern">
|
||||
<!--
|
||||
This configuration is available on the major desktop environments.
|
||||
We shouldn't overwrite it with "assign" unconditionally.
|
||||
Most clients may picks up the first value only. so using "append"
|
||||
may simply works to avoid it.
|
||||
-->
|
||||
<edit mode="append" name="lcdfilter">
|
||||
<const>lcdlight</const>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
The Bitstream Vera fonts have GASP entries suggesting that hinting be
|
||||
disabled below 8 ppem, but FreeType ignores those, preferring to use
|
||||
the data found in the instructed hints. The initial Vera release
|
||||
didn't include the right instructions in the 'prep' table. Fix this
|
||||
by disabling hinting manually at smaller sizes (< 8ppem)
|
||||
-->
|
||||
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Bitstream Vera Sans</string>
|
||||
</test>
|
||||
<test name="pixelsize" compare="less">
|
||||
<double>7.5</double>
|
||||
</test>
|
||||
<edit name="hinting">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Bitstream Vera Serif</string>
|
||||
</test>
|
||||
<test name="pixelsize" compare="less">
|
||||
<double>7.5</double>
|
||||
</test>
|
||||
<edit name="hinting">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Bitstream Vera Sans Mono</string>
|
||||
</test>
|
||||
<test name="pixelsize" compare="less">
|
||||
<double>7.5</double>
|
||||
</test>
|
||||
<edit name="hinting">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,128 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
|
||||
<!-- We can't hint CJK fonts well, so turn off hinting for CJK fonts. -->
|
||||
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Kochi Mincho</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Kochi Gothic</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Sazanami Mincho</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Sazanami Gothic</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Baekmuk Batang</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Baekmuk Dotum</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Baekmuk Gulim</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Baekmuk Headline</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL Mingti2L Big5</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL ShanHeiSun Uni</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL KaitiM Big5</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL ZenKai Uni</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL SungtiL GB</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>AR PL KaitiM GB</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
<match target="font">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>ZYSong18030</string>
|
||||
</test>
|
||||
<edit name="hinting" mode="assign">
|
||||
<bool>false</bool>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,652 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
|
||||
<!--
|
||||
|
||||
Alias similar/metric-compatible families from various sources:
|
||||
|
||||
PostScript fonts: URW fonts: GUST fonts: Windows fonts:
|
||||
====================== ================== ================= ==================
|
||||
Helvetica Nimbus Sans TeX Gyre Heros
|
||||
Helvetica Narrow Nimbus Sans Narrow TeX Gyre Heros Cn
|
||||
Times Nimbus Roman TeX Gyre Termes
|
||||
Courier Nimbus Mono PS TeX Gyre Cursor
|
||||
ITC Avant Garde Gothic URW Gothic TeX Gyre Adventor
|
||||
ITC Bookman URW Bookman TeX Gyre Bonum Bookman Old Style
|
||||
ITC Zapf Chancery Z003 TeX Gyre Chorus
|
||||
Palatino P052 TeX Gyre Pagella Palatino Linotype
|
||||
New Century Schoolbook C059 TeX Gyre Schola Century Schoolbook
|
||||
|
||||
Microsoft fonts: Liberation fonts: Google CrOS core fonts: StarOffice fonts: AMT fonts:
|
||||
================ ====================== ======================= ================= ==============
|
||||
Arial Liberation Sans Arimo Albany Albany AMT
|
||||
Arial Narrow Liberation Sans Narrow
|
||||
Times New Roman Liberation Serif Tinos Thorndale Thorndale AMT
|
||||
Courier New Liberation Mono Cousine Cumberland Cumberland AMT
|
||||
Cambria Caladea
|
||||
Calibri Carlito
|
||||
Symbol SymbolNeu
|
||||
|
||||
Microsoft fonts: Other fonts:
|
||||
================ ============
|
||||
Georgia Gelasio
|
||||
|
||||
We want for each of them to fallback to any of these available,
|
||||
but in an order preferring similar designs first. We do this in three steps:
|
||||
|
||||
1) Alias each specific to its generic family.
|
||||
e.g. Liberation Sans to Arial
|
||||
|
||||
2) Weak alias each generic to the other generic of its family.
|
||||
e.g. Arial to Helvetica
|
||||
|
||||
3) Alias each generic to its specifics.
|
||||
e.g. Arial to Liberation Sans, Arimo, Albany, and Albany AMT
|
||||
|
||||
-->
|
||||
|
||||
<!-- Map specifics to generics -->
|
||||
|
||||
<!-- PostScript -->
|
||||
<alias binding="same">
|
||||
<family>Nimbus Sans L</family>
|
||||
<default>
|
||||
<family>Helvetica</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Sans</family>
|
||||
<default>
|
||||
<family>Helvetica</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Heros</family>
|
||||
<default>
|
||||
<family>Helvetica</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Sans Narrow</family>
|
||||
<default>
|
||||
<family>Helvetica Narrow</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Heros Cn</family>
|
||||
<default>
|
||||
<family>Helvetica Narrow</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Roman No9 L</family>
|
||||
<default>
|
||||
<family>Times</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Roman</family>
|
||||
<default>
|
||||
<family>Times</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Termes</family>
|
||||
<default>
|
||||
<family>Times</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Mono L</family>
|
||||
<default>
|
||||
<family>Courier</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Mono</family>
|
||||
<default>
|
||||
<family>Courier</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Nimbus Mono PS</family>
|
||||
<default>
|
||||
<family>Courier</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Cursor</family>
|
||||
<default>
|
||||
<family>Courier</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Avant Garde</family>
|
||||
<default>
|
||||
<family>ITC Avant Garde Gothic</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Gothic L</family>
|
||||
<default>
|
||||
<family>ITC Avant Garde Gothic</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Gothic</family>
|
||||
<default>
|
||||
<family>ITC Avant Garde Gothic</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Adventor</family>
|
||||
<default>
|
||||
<family>ITC Avant Garde Gothic</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Bookman</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Bookman L</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Bookman URW</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Bookman</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Bonum</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Bookman Old Style</family>
|
||||
<default>
|
||||
<family>ITC Bookman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Zapf Chancery</family>
|
||||
<default>
|
||||
<family>ITC Zapf Chancery</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Chancery L</family>
|
||||
<default>
|
||||
<family>ITC Zapf Chancery</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Chancery URW</family>
|
||||
<default>
|
||||
<family>ITC Zapf Chancery</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Z003</family>
|
||||
<default>
|
||||
<family>ITC Zapf Chancery</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Chorus</family>
|
||||
<default>
|
||||
<family>ITC Zapf Chancery</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>URW Palladio L</family>
|
||||
<default>
|
||||
<family>Palatino</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Palladio URW</family>
|
||||
<default>
|
||||
<family>Palatino</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>P052</family>
|
||||
<default>
|
||||
<family>Palatino</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Pagella</family>
|
||||
<default>
|
||||
<family>Palatino</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Palatino Linotype</family>
|
||||
<default>
|
||||
<family>Palatino</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Century Schoolbook L</family>
|
||||
<default>
|
||||
<family>New Century Schoolbook</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Century SchoolBook URW</family>
|
||||
<default>
|
||||
<family>New Century Schoolbook</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>C059</family>
|
||||
<default>
|
||||
<family>New Century Schoolbook</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>TeX Gyre Schola</family>
|
||||
<default>
|
||||
<family>New Century Schoolbook</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Century Schoolbook</family>
|
||||
<default>
|
||||
<family>New Century Schoolbook</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
|
||||
<!-- Microsoft -->
|
||||
<alias binding="same">
|
||||
<family>Arimo</family>
|
||||
<default>
|
||||
<family>Arial</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Liberation Sans</family>
|
||||
<default>
|
||||
<family>Arial</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Liberation Sans Narrow</family>
|
||||
<default>
|
||||
<family>Arial Narrow</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Albany</family>
|
||||
<default>
|
||||
<family>Arial</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Albany AMT</family>
|
||||
<default>
|
||||
<family>Arial</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Tinos</family>
|
||||
<default>
|
||||
<family>Times New Roman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Liberation Serif</family>
|
||||
<default>
|
||||
<family>Times New Roman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Thorndale</family>
|
||||
<default>
|
||||
<family>Times New Roman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Thorndale AMT</family>
|
||||
<default>
|
||||
<family>Times New Roman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Cousine</family>
|
||||
<default>
|
||||
<family>Courier New</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Liberation Mono</family>
|
||||
<default>
|
||||
<family>Courier New</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Cumberland</family>
|
||||
<default>
|
||||
<family>Courier New</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Cumberland AMT</family>
|
||||
<default>
|
||||
<family>Courier New</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Gelasio</family>
|
||||
<default>
|
||||
<family>Georgia</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Caladea</family>
|
||||
<default>
|
||||
<family>Cambria</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Carlito</family>
|
||||
<default>
|
||||
<family>Calibri</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>SymbolNeu</family>
|
||||
<default>
|
||||
<family>Symbol</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<!-- Accept the other group as fallback -->
|
||||
|
||||
<!-- PostScript -->
|
||||
<alias>
|
||||
<family>Helvetica</family>
|
||||
<default>
|
||||
<family>Arial</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Helvetica Narrow</family>
|
||||
<default>
|
||||
<family>Arial Narrow</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Times</family>
|
||||
<default>
|
||||
<family>Times New Roman</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Courier</family>
|
||||
<default>
|
||||
<family>Courier New</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
|
||||
<!-- Microsoft -->
|
||||
<alias>
|
||||
<family>Arial</family>
|
||||
<default>
|
||||
<family>Helvetica</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Arial Narrow</family>
|
||||
<default>
|
||||
<family>Helvetica Narrow</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Times New Roman</family>
|
||||
<default>
|
||||
<family>Times</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
<alias>
|
||||
<family>Courier New</family>
|
||||
<default>
|
||||
<family>Courier</family>
|
||||
</default>
|
||||
</alias>
|
||||
|
||||
|
||||
|
||||
<!-- Map generics to specifics -->
|
||||
|
||||
<!-- PostScript -->
|
||||
<alias binding="same">
|
||||
<family>Helvetica</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Heros</family>
|
||||
<family>Nimbus Sans</family>
|
||||
<family>Nimbus Sans L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Helvetica Narrow</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Heros Cn</family>
|
||||
<family>Nimbus Sans Narrow</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Times</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Termes</family>
|
||||
<family>Nimbus Roman</family>
|
||||
<family>Nimbus Roman No9 L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Courier</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Cursor</family>
|
||||
<family>Nimbus Mono PS</family>
|
||||
<family>Nimbus Mono</family>
|
||||
<family>Nimbus Mono L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>ITC Avant Garde Gothic</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Adventor</family>
|
||||
<family>URW Gothic</family>
|
||||
<family>URW Gothic L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>ITC Bookman</family>
|
||||
<accept>
|
||||
<family>Bookman Old Style</family>
|
||||
<family>TeX Gyre Bonum</family>
|
||||
<family>URW Bookman</family>
|
||||
<family>Bookman URW</family>
|
||||
<family>URW Bookman L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>ITC Zapf Chancery</family>
|
||||
<accept>
|
||||
<family>TeX Gyre Chorus</family>
|
||||
<family>Z003</family>
|
||||
<family>Chancery URW</family>
|
||||
<family>URW Chancery L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Palatino</family>
|
||||
<accept>
|
||||
<family>Palatino Linotype</family>
|
||||
<family>TeX Gyre Pagella</family>
|
||||
<family>P052</family>
|
||||
<family>Palladio URW</family>
|
||||
<family>URW Palladio L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>New Century Schoolbook</family>
|
||||
<accept>
|
||||
<family>Century Schoolbook</family>
|
||||
<family>TeX Gyre Schola</family>
|
||||
<family>C059</family>
|
||||
<family>Century SchoolBook URW</family>
|
||||
<family>Century Schoolbook L</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<!-- Microsoft -->
|
||||
<alias binding="same">
|
||||
<family>Arial</family>
|
||||
<accept>
|
||||
<family>Arimo</family>
|
||||
<family>Liberation Sans</family>
|
||||
<family>Albany</family>
|
||||
<family>Albany AMT</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Arial Narrow</family>
|
||||
<accept>
|
||||
<family>Liberation Sans Narrow</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Times New Roman</family>
|
||||
<accept>
|
||||
<family>Tinos</family>
|
||||
<family>Liberation Serif</family>
|
||||
<family>Thorndale</family>
|
||||
<family>Thorndale AMT</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Courier New</family>
|
||||
<accept>
|
||||
<family>Cousine</family>
|
||||
<family>Liberation Mono</family>
|
||||
<family>Cumberland</family>
|
||||
<family>Cumberland AMT</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Georgia</family>
|
||||
<accept>
|
||||
<family>Gelasio</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Cambria</family>
|
||||
<accept>
|
||||
<family>Caladea</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Calibri</family>
|
||||
<accept>
|
||||
<family>Carlito</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
<alias binding="same">
|
||||
<family>Symbol</family>
|
||||
<accept>
|
||||
<family>SymbolNeu</family>
|
||||
</accept>
|
||||
</alias>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
URW provides metric and shape compatible fonts for some Adobe families.
|
||||
Most of these are handled in 30-metric-aliases.conf.
|
||||
-->
|
||||
<alias binding="same">
|
||||
<family>Zapf Dingbats</family>
|
||||
<accept>
|
||||
<family>D050000L</family>
|
||||
<family>Dingbats</family>
|
||||
</accept>
|
||||
</alias>
|
||||
<alias binding="same">
|
||||
<family>ITC Zapf Dingbats</family>
|
||||
<accept>
|
||||
<family>D050000L</family>
|
||||
<family>Dingbats</family>
|
||||
</accept>
|
||||
</alias>
|
||||
<match target="pattern">
|
||||
<test name="family" compare="eq" ignore-blanks="true">
|
||||
<string>Symbol</string>
|
||||
</test>
|
||||
<edit name="family" mode="append" binding="same">
|
||||
<string>Standard Symbols PS</string>
|
||||
</edit>
|
||||
<edit name="family" mode="append" binding="same">
|
||||
<string>Standard Symbols L</string>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,231 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
Mark common families with their generics so we'll get
|
||||
something reasonable
|
||||
-->
|
||||
|
||||
<!--
|
||||
Serif faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Nazli</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Lotoos</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Mitra</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Ferdosi</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Badr</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Zar</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Titr</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Jadid</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Kochi Mincho</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL SungtiL GB</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL Mingti2L Big5</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MS 明朝</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>NanumMyeongjo</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>UnBatang</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Baekmuk Batang</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MgOpen Canonica</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Sazanami Mincho</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL ZenKai Uni</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>ZYSong18030</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>FreeSerif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>SimSun</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Sans-serif faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Arshia</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Elham</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Farnaz</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nasim</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Sina</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Roya</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Koodak</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Terafik</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Kochi Gothic</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL KaitiM GB</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL KaitiM Big5</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MS ゴシック</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>NanumGothic</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>UnDotum</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Baekmuk Dotum</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MgOpen Modata</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Sazanami Gothic</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>AR PL ShanHeiSun Uni</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>ZYSong18030</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>FreeSans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Monospace faces
|
||||
-->
|
||||
<alias>
|
||||
<family>NSimSun</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>ZYSong18030</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>NanumGothicCoding</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>FreeMono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
|
||||
<!--
|
||||
Fantasy faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Homa</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Kamran</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Fantezi</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Tabassom</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
|
||||
<!--
|
||||
Cursive faces
|
||||
-->
|
||||
<alias>
|
||||
<family>IranNastaliq</family>
|
||||
<default><family>cursive</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nafees Nastaleeq</family>
|
||||
<default><family>cursive</family></default>
|
||||
</alias>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,273 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
Mark common families with their generics so we'll get
|
||||
something reasonable
|
||||
-->
|
||||
|
||||
<!--
|
||||
Serif faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Bitstream Vera Serif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Cambria</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Constantia</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>DejaVu Serif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Elephant</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Garamond</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Georgia</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Liberation Serif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Luxi Serif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MS Serif</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Roman No9 L</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Roman</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Palatino Linotype</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Thorndale AMT</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Thorndale</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Times New Roman</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Times</family>
|
||||
<default><family>serif</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Sans-serif faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Albany AMT</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Albany</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Arial Unicode MS</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Arial</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Bitstream Vera Sans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Britannic</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Calibri</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Candara</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Century Gothic</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Corbel</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>DejaVu Sans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Helvetica</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Haettenschweiler</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Liberation Sans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>MS Sans Serif</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Sans L</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Sans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Luxi Sans</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Tahoma</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Trebuchet MS</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Twentieth Century</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Verdana</family>
|
||||
<default><family>sans-serif</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Monospace faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Andale Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Bitstream Vera Sans Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Consolas</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Courier New</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Courier</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Cumberland AMT</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Cumberland</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>DejaVu Sans Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Fixedsys</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Inconsolata</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Liberation Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Luxi Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Mono L</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Mono</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Nimbus Mono PS</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Terminal</family>
|
||||
<default><family>monospace</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Fantasy faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Bauhaus Std</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Cooper Std</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Copperplate Gothic Std</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Impact</family>
|
||||
<default><family>fantasy</family></default>
|
||||
</alias>
|
||||
<!--
|
||||
Cursive faces
|
||||
-->
|
||||
<alias>
|
||||
<family>Comic Sans MS</family>
|
||||
<default><family>cursive</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>ITC Zapf Chancery Std</family>
|
||||
<default><family>cursive</family></default>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>Zapfino</family>
|
||||
<default><family>cursive</family></default>
|
||||
</alias>
|
||||
|
||||
</fontconfig>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
If the font still has no generic name, add sans-serif
|
||||
-->
|
||||
<match target="pattern">
|
||||
<test qual="all" name="family" compare="not_eq">
|
||||
<string>sans-serif</string>
|
||||
</test>
|
||||
<test qual="all" name="family" compare="not_eq">
|
||||
<string>serif</string>
|
||||
</test>
|
||||
<test qual="all" name="family" compare="not_eq">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="append_last">
|
||||
<string>sans-serif</string>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!--
|
||||
Load per-user customization files where stored on XDG Base Directory
|
||||
specification compliant places. it should be usually:
|
||||
$HOME/.config/fontconfig/conf.d
|
||||
$HOME/.config/fontconfig/fonts.conf
|
||||
-->
|
||||
<include ignore_missing="yes" prefix="xdg">fontconfig/conf.d</include>
|
||||
<include ignore_missing="yes" prefix="xdg">fontconfig/fonts.conf</include>
|
||||
<!-- the following elements will be removed in the future -->
|
||||
<include ignore_missing="yes" deprecated="yes">~/.fonts.conf.d</include>
|
||||
<include ignore_missing="yes" deprecated="yes">~/.fonts.conf</include>
|
||||
</fontconfig>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<!-- Load local system customization file -->
|
||||
<include ignore_missing="yes">local.conf</include>
|
||||
</fontconfig>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user