forked from OpenGamers/abaddon
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad0d4e7d4d | ||
|
|
1ca6235e09 | ||
|
|
c04fc2e60c | ||
|
|
a7bf9a2404 | ||
|
|
2065ef4940 | ||
|
|
3aefab652e |
200
.github/workflows/ci.yml
vendored
200
.github/workflows/ci.yml
vendored
@@ -1,145 +1,79 @@
|
||||
name: Abaddon CI
|
||||
|
||||
on: [ push, pull_request ]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
msys2:
|
||||
name: msys2-mingw64
|
||||
windows:
|
||||
name: windows-${{ matrix.buildtype }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [ Debug, RelWithDebInfo, MinSizeRel ]
|
||||
mindeps: [ false ]
|
||||
include:
|
||||
- buildtype: RelWithDebInfo
|
||||
mindeps: true
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup MSYS2 (1)
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: setupmsys
|
||||
with:
|
||||
cond: ${{ matrix.mindeps == true }}
|
||||
if_true: >-
|
||||
git
|
||||
make
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-ninja
|
||||
mingw-w64-x86_64-sqlite3
|
||||
mingw-w64-x86_64-nlohmann-json
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-gtkmm3
|
||||
mingw-w64-x86_64-spdlog
|
||||
if_false: >-
|
||||
git
|
||||
make
|
||||
mingw-w64-x86_64-toolchain
|
||||
mingw-w64-x86_64-cmake
|
||||
mingw-w64-x86_64-ninja
|
||||
mingw-w64-x86_64-sqlite3
|
||||
mingw-w64-x86_64-nlohmann-json
|
||||
mingw-w64-x86_64-curl
|
||||
mingw-w64-x86_64-zlib
|
||||
mingw-w64-x86_64-gtkmm3
|
||||
mingw-w64-x86_64-libhandy
|
||||
mingw-w64-x86_64-opus
|
||||
mingw-w64-x86_64-libsodium
|
||||
mingw-w64-x86_64-spdlog
|
||||
|
||||
- name: Setup MSYS2 (2)
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: mingw64
|
||||
update: true
|
||||
install: ${{ steps.setupmsys.outputs.value }}
|
||||
|
||||
- name: Build (1)
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: buildcmd
|
||||
with:
|
||||
cond: ${{ matrix.mindeps == true }}
|
||||
if_true: |
|
||||
cmake -GNinja -Bbuild -DUSE_LIBHANDY=OFF -DENABLE_VOICE=OFF -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }}
|
||||
cmake --build build
|
||||
if_false: |
|
||||
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }}
|
||||
cmake --build build
|
||||
|
||||
- name: Build (2)
|
||||
run: ${{ steps.buildcmd.outputs.value }}
|
||||
|
||||
- name: Setup Artifact
|
||||
run: |
|
||||
mkdir -p build/artifactdir/bin build/artifactdir/ssl/certs build/artifactdir/lib build/artifactdir/share/glib-2.0/schemas
|
||||
cd build
|
||||
cp *.exe artifactdir/bin
|
||||
cd ..
|
||||
cp /mingw64/ssl/certs/ca-bundle.crt build/artifactdir/ssl/certs
|
||||
cp -r /mingw64/lib/gdk-pixbuf-2.0 build/artifactdir/lib
|
||||
cp -r res/css res/res res/fonts build/artifactdir/bin
|
||||
cp /mingw64/share/glib-2.0/schemas/gschemas.compiled build/artifactdir/share/glib-2.0/schemas
|
||||
cat "ci/msys-deps.txt" | sed 's/\r$//' | xargs -I % cp /mingw64% build/artifactdir/bin || :
|
||||
cp /usr/bin/msys-ffi-8.dll build/artifactdir/bin/libffi-8.dll
|
||||
mkdir -p build/artifactdir/share/icons/Adwaita
|
||||
cd build/artifactdir/share/icons/Adwaita
|
||||
mkdir -p 16x16/actions 24x24/actions 32x32/actions 48x48/actions 64x64/actions 96x96/actions scalable/actions
|
||||
cd ../../../../../
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/16x16/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/16x16/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/24x24/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/24x24/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/32x32/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/32x32/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/48x48/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/48x48/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/64x64/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/64x64/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/96x96/actions/%.symbolic.png build/artifactdir/share/icons/Adwaita/96x96/actions || :
|
||||
cat "ci/used-icons.txt" | sed 's/\r$//' | xargs -I % cp ci/gtk-for-windows/gtk-nsis-pack/share/icons/Adwaita/scalable/actions/%.svg build/artifactdir/share/icons/Adwaita/scalable/actions || :
|
||||
|
||||
- name: Upload build (1)
|
||||
uses: haya14busa/action-cond@v1
|
||||
id: buildname
|
||||
with:
|
||||
cond: ${{ matrix.mindeps == true }}
|
||||
if_true: "${{ matrix.buildtype }}-mindeps"
|
||||
if_false: "${{ matrix.buildtype }}"
|
||||
|
||||
- name: Upload build (2)
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-windows-msys2-${{ steps.buildname.outputs.value }}
|
||||
path: build/artifactdir
|
||||
|
||||
mac:
|
||||
name: macos-${{ matrix.buildtype }}
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [ Debug, RelWithDebInfo ]
|
||||
buildtype: [Debug, RelWithDebInfo]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Fetch dependencies
|
||||
uses: lukka/run-vcpkg@main
|
||||
with:
|
||||
vcpkgArguments: gtkmm nlohmann-json zlib sqlite3 glibmm openssl ixwebsocket curl
|
||||
vcpkgDirectory: ${{ github.workspace }}/ci/vcpkg/
|
||||
vcpkgTriplet: x64-windows
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@main
|
||||
with:
|
||||
useVcpkgToolchainFile: true
|
||||
vcpkgTriplet: x64-windows
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeBuildType: ${{ matrix.buildtype }}
|
||||
|
||||
- name: Setup artifact files
|
||||
shell: cmd
|
||||
run: |
|
||||
del /f /s /q "${{ runner.workspace }}\build\CMakeFiles"
|
||||
rmdir /s /q "${{ runner.workspace }}\build\CMakeFiles"
|
||||
del /f /s /q "${{ runner.workspace }}\build\.ninja_deps"
|
||||
del /f /s /q "${{ runner.workspace }}\build\.ninja_log"
|
||||
del /f /s /q "${{ runner.workspace }}\build\abaddon.ilk"
|
||||
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"
|
||||
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"
|
||||
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper-console.exe" "${{ runner.workspace }}\build\gspawn-win64-helper-console.exe"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-windows-${{ matrix.buildtype }}
|
||||
path: ${{ runner.workspace }}/build
|
||||
mac:
|
||||
name: macos-${{ matrix.buildtype }}
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [Debug, RelWithDebInfo]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
brew install gtkmm3
|
||||
brew install nlohmann-json
|
||||
brew install jpeg
|
||||
brew install opus
|
||||
brew install libsodium
|
||||
brew install spdlog
|
||||
brew install libhandy
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@v3
|
||||
uses: lukka/run-cmake@main
|
||||
with:
|
||||
buildDirectory: ${{ runner.workspace }}/build
|
||||
cmakeBuildType: ${{ matrix.buildtype }}
|
||||
@@ -148,8 +82,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -159,17 +93,17 @@ jobs:
|
||||
|
||||
linux:
|
||||
name: linux-${{ matrix.buildtype }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildtype: [ Debug, RelWithDebInfo, MinSizeRel ]
|
||||
buildtype: [Debug, RelWithDebInfo, MinSizeRel]
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Fetch CMake
|
||||
uses: lukka/get-cmake@v3.21.2
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Fetch dependencies
|
||||
run: |
|
||||
@@ -178,7 +112,7 @@ jobs:
|
||||
cd deps
|
||||
git clone https://github.com/nlohmann/json
|
||||
cd json
|
||||
git checkout bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d
|
||||
git checkout db78ac1d7716f56fc9f1b030b715f872f93964e4
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
@@ -186,13 +120,9 @@ jobs:
|
||||
sudo make install
|
||||
sudo apt-get install libgtkmm-3.0-dev
|
||||
sudo apt-get install libcurl4-gnutls-dev
|
||||
sudo apt-get install libopus-dev
|
||||
sudo apt-get install libsodium-dev
|
||||
sudo apt-get install libspdlog-dev
|
||||
sudo apt-get install libhandy-1-dev
|
||||
|
||||
- name: Build
|
||||
uses: lukka/run-cmake@v3
|
||||
uses: lukka/run-cmake@main
|
||||
env:
|
||||
CC: gcc-9
|
||||
CXX: g++-9
|
||||
@@ -205,8 +135,8 @@ jobs:
|
||||
run: |
|
||||
mkdir "${{ runner.workspace }}/artifactdir"
|
||||
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
|
||||
cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
|
||||
cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v2
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -354,11 +354,3 @@ testdata/
|
||||
|
||||
build/
|
||||
out/
|
||||
|
||||
fonts/fonts.conf
|
||||
|
||||
# To make sure no zipped resources are added to the repo
|
||||
*.7z
|
||||
*.zip
|
||||
*.tar.*
|
||||
*.rar
|
||||
|
||||
18
.gitmodules
vendored
18
.gitmodules
vendored
@@ -1,9 +1,15 @@
|
||||
[submodule "vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg/
|
||||
[submodule "thirdparty/simpleini"]
|
||||
path = thirdparty/simpleini
|
||||
url = https://github.com/brofield/simpleini
|
||||
[submodule "thirdparty/IXWebSocket"]
|
||||
path = thirdparty/IXWebSocket
|
||||
url = https://github.com/machinezone/ixwebsocket
|
||||
[submodule "ci/vcpkg"]
|
||||
path = ci/vcpkg
|
||||
url = https://github.com/microsoft/vcpkg
|
||||
[submodule "ci/gtk-for-windows"]
|
||||
path = ci/gtk-for-windows
|
||||
url = https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer
|
||||
[submodule "subprojects/ixwebsocket"]
|
||||
path = subprojects/ixwebsocket
|
||||
url = https://github.com/ouwou/ixwebsocket
|
||||
[submodule "subprojects/miniaudio"]
|
||||
path = subprojects/miniaudio
|
||||
url = https://github.com/mackron/miniaudio
|
||||
|
||||
147
CMakeLists.txt
147
CMakeLists.txt
@@ -2,13 +2,11 @@ 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/")
|
||||
|
||||
option(USE_LIBHANDY "Enable features that require libhandy (default)" ON)
|
||||
option(ENABLE_VOICE "Enable voice suppport" ON)
|
||||
set(USE_TLS TRUE)
|
||||
set(USE_OPEN_SSL TRUE)
|
||||
|
||||
find_package(nlohmann_json REQUIRED)
|
||||
find_package(CURL)
|
||||
@@ -16,112 +14,79 @@ 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")
|
||||
add_subdirectory(subprojects/ixwebsocket)
|
||||
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
|
||||
endif ()
|
||||
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()
|
||||
|
||||
if (MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
endif ()
|
||||
include_directories(thirdparty/simpleini)
|
||||
|
||||
if (WIN32)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif ()
|
||||
if(MINGW OR WIN32)
|
||||
link_libraries(ws2_32)
|
||||
endif()
|
||||
|
||||
include(TestBigEndian)
|
||||
test_big_endian(IS_BIG_ENDIAN)
|
||||
if (IS_BIG_ENDIAN)
|
||||
add_compile_definitions(ABADDON_IS_BIG_ENDIAN)
|
||||
endif ()
|
||||
if(WIN32)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(NOMINMAX)
|
||||
endif()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||
|
||||
file(GLOB_RECURSE ABADDON_SOURCES
|
||||
"src/*.h"
|
||||
"src/*.hpp"
|
||||
"src/*.cpp"
|
||||
)
|
||||
file(GLOB ABADDON_SOURCES
|
||||
"*.h"
|
||||
"*.hpp"
|
||||
"*.cpp"
|
||||
"discord/*.hpp"
|
||||
"discord/*.cpp"
|
||||
"components/*.hpp"
|
||||
"components/*.cpp"
|
||||
"windows/*.hpp"
|
||||
"windows/*.cpp"
|
||||
"windows/guildsettings/*.hpp"
|
||||
"windows/guildsettings/*.cpp"
|
||||
"windows/profile/*.hpp"
|
||||
"windows/profile/*.cpp"
|
||||
"dialogs/*.hpp"
|
||||
"dialogs/*.cpp"
|
||||
)
|
||||
|
||||
add_executable(abaddon ${ABADDON_SOURCES})
|
||||
target_include_directories(abaddon PUBLIC ${PROJECT_SOURCE_DIR}/src)
|
||||
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})
|
||||
target_include_directories(abaddon PUBLIC ${NLOHMANN_JSON_INCLUDE_DIRS})
|
||||
|
||||
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR (CMAKE_CXX_COMPILER_VERSION LESS 9))))
|
||||
target_link_libraries(abaddon stdc++fs)
|
||||
endif ()
|
||||
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND
|
||||
((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR (CMAKE_CXX_COMPILER_VERSION LESS 9))))
|
||||
target_link_libraries(abaddon stdc++fs)
|
||||
endif()
|
||||
|
||||
if (IXWebSocket_LIBRARIES)
|
||||
target_link_libraries(abaddon ${IXWebSocket_LIBRARIES})
|
||||
find_library(MBEDTLS_X509_LIBRARY mbedx509)
|
||||
find_library(MBEDTLS_TLS_LIBRARY mbedtls)
|
||||
find_library(MBEDTLS_CRYPTO_LIBRARY mbedcrypto)
|
||||
if (MBEDTLS_TLS_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_TLS_LIBRARY})
|
||||
endif ()
|
||||
if (MBEDTLS_X509_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_X509_LIBRARY})
|
||||
endif ()
|
||||
if (MBEDTLS_CRYPTO_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
endif ()
|
||||
else ()
|
||||
target_link_libraries(abaddon $<BUILD_INTERFACE:ixwebsocket>)
|
||||
endif ()
|
||||
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)
|
||||
if (MBEDTLS_TLS_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_TLS_LIBRARY})
|
||||
endif()
|
||||
if (MBEDTLS_X509_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_X509_LIBRARY})
|
||||
endif()
|
||||
if (MBEDTLS_CRYPTO_LIBRARY)
|
||||
target_link_libraries(abaddon ${MBEDTLS_CRYPTO_LIBRARY})
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(abaddon $<BUILD_INTERFACE:ixwebsocket>)
|
||||
endif()
|
||||
|
||||
find_package(Threads)
|
||||
if (Threads_FOUND)
|
||||
target_link_libraries(abaddon Threads::Threads)
|
||||
endif ()
|
||||
|
||||
find_package(Fontconfig QUIET)
|
||||
if (Fontconfig_FOUND)
|
||||
target_link_libraries(abaddon Fontconfig::Fontconfig)
|
||||
endif ()
|
||||
|
||||
find_package(spdlog REQUIRED)
|
||||
target_link_libraries(abaddon spdlog::spdlog)
|
||||
target_link_libraries(abaddon Threads::Threads)
|
||||
endif()
|
||||
|
||||
target_link_libraries(abaddon ${SQLite3_LIBRARIES})
|
||||
target_link_libraries(abaddon ${GTKMM_LIBRARIES})
|
||||
target_link_libraries(abaddon ${CURL_LIBRARIES})
|
||||
target_link_libraries(abaddon ${ZLIB_LIBRARY})
|
||||
target_link_libraries(abaddon ${NLOHMANN_JSON_LIBRARIES})
|
||||
|
||||
if (USE_LIBHANDY)
|
||||
find_package(libhandy)
|
||||
if (NOT libhandy_FOUND)
|
||||
message("libhandy could not be found. features requiring it have been disabled")
|
||||
set(USE_LIBHANDY OFF)
|
||||
else ()
|
||||
target_include_directories(abaddon PUBLIC ${libhandy_INCLUDE_DIRS})
|
||||
target_link_libraries(abaddon ${libhandy_LIBRARIES})
|
||||
target_compile_definitions(abaddon PRIVATE WITH_LIBHANDY)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (ENABLE_VOICE)
|
||||
target_compile_definitions(abaddon PRIVATE WITH_VOICE)
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
target_include_directories(abaddon PUBLIC subprojects/miniaudio)
|
||||
pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus)
|
||||
target_link_libraries(abaddon PkgConfig::Opus)
|
||||
|
||||
pkg_check_modules(libsodium REQUIRED IMPORTED_TARGET libsodium)
|
||||
target_link_libraries(abaddon PkgConfig::libsodium)
|
||||
|
||||
target_link_libraries(abaddon ${CMAKE_DL_LIBS})
|
||||
endif ()
|
||||
|
||||
206
README.md
206
README.md
@@ -4,16 +4,12 @@ Alternative Discord client made in C++ with GTK
|
||||
|
||||
<img src="/.readme/s3.png">
|
||||
|
||||
<a href="https://discord.gg/wkCU3vuzG5"><img src="https://discord.com/api/guilds/858156817711890443/widget.png?style=shield"></a>
|
||||
|
||||
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
|
||||
* Unread and mention indicators
|
||||
* Start new DMs and group DMs
|
||||
* View user profiles (notes, mutual servers, mutual friends)
|
||||
* Kick, ban, and unban members
|
||||
@@ -22,124 +18,73 @@ 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 (though is not perfect) 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.<br>
|
||||
|
||||
**See [here](#the-spam-filter)** for things you might want to avoid if you are worried about being caught in the spam
|
||||
filter.
|
||||
|
||||
2 - Unicode emojis are substituted 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 (with MSYS2):
|
||||
|
||||
1. Install following packages:
|
||||
* mingw-w64-x86_64-cmake
|
||||
* mingw-w64-x86_64-ninja
|
||||
* mingw-w64-x86_64-sqlite3
|
||||
* mingw-w64-x86_64-nlohmann-json
|
||||
* mingw-w64-x86_64-curl
|
||||
* mingw-w64-x86_64-zlib
|
||||
* mingw-w64-x86_64-gtkmm3
|
||||
* mingw-w64-x86_64-libhandy
|
||||
2. `git clone --recurse-submodules="subprojects" https://github.com/uowuo/abaddon && cd abaddon`
|
||||
#### Windows:
|
||||
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows simpleini:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo ..`
|
||||
5. `ninja`
|
||||
4. `cmake -G"Visual Studio 16 2019" -A x64 -DCMAKE_TOOLCHAIN_FILE=c:\path\to\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DVCPKG_TARGET_TRIPLET=x64-windows ..`
|
||||
5. Build with Visual Studio
|
||||
|
||||
Or, do steps 1 and 2, and open CMakeLists.txt in Visual Studio if `vcpkg integrate install` was run
|
||||
|
||||
#### Mac:
|
||||
|
||||
1. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
|
||||
2. `brew install gtkmm3 nlohmann-json libhandy`
|
||||
1. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
2. `brew install gtkmm3 nlohmann-json`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake ..`
|
||||
5. `make`
|
||||
|
||||
#### Linux:
|
||||
|
||||
1. Install dependencies
|
||||
* On Ubuntu 20.04 (Focal) and newer:
|
||||
```Shell
|
||||
$ sudo apt install g++ cmake libgtkmm-3.0-dev libcurl4-gnutls-dev libsqlite3-dev libssl-dev nlohmann-json3-dev
|
||||
```
|
||||
2. `git clone https://github.com/uowuo/abaddon --recurse-submodules="subprojects" && cd abaddon`
|
||||
1. Install dependencies: `libgtkmm-3.0-dev`, `libcurl4-gnutls-dev`, and [nlohmann-json](https://github.com/nlohmann/json)
|
||||
2. `git clone https://github.com/uowuo/abaddon && cd abaddon`
|
||||
3. `mkdir build && cd build`
|
||||
4. `cmake ..`
|
||||
5. `make`
|
||||
|
||||
### Downloads:
|
||||
### 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
|
||||
|
||||
Latest release version: https://github.com/uowuo/abaddon/releases/latest
|
||||
|
||||
**CI:**
|
||||
|
||||
- Windows: [here](https://nightly.link/uowuo/abaddon/workflows/ci/master/build-windows-msys2-MinSizeRel.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 `bin` directory
|
||||
|
||||
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
|
||||
|
||||
#### The Spam Filter
|
||||
|
||||
Discord likes disabling accounts/forcing them to reset their passwords if they think the user is a spam bot or
|
||||
potentially had their account compromised. While the official client still often gets users caught in the spam filter,
|
||||
third party clients tend to upset the spam filter more often. If you get caught by it, you can
|
||||
usually [appeal](https://support.discord.com/hc/en-us/requests/new?ticket_form_id=360000029731) it and get it restored.
|
||||
Here are some things you might want to do with the official client instead if you are particularly afraid of evoking the
|
||||
spam filter's wrath:
|
||||
|
||||
* Joining or leaving servers (usually main cause of getting caught)
|
||||
* Frequently disconnecting and reconnecting
|
||||
* Starting new DMs with people
|
||||
* Managing your friends list
|
||||
* Managing your user profile while connected to a third party client
|
||||
|
||||
#### Dependencies:
|
||||
⚠️ 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/)
|
||||
* [JSON for Modern C++](https://github.com/nlohmann/json)
|
||||
* [IXWebSocket](https://github.com/machinezone/IXWebSocket)
|
||||
* [libcurl](https://curl.se/)
|
||||
* [zlib](https://zlib.net/)
|
||||
* [simpleini](https://github.com/brofield/simpleini)
|
||||
* [SQLite3](https://www.sqlite.org/index.html)
|
||||
* [libhandy](https://gnome.pages.gitlab.gnome.org/libhandy/) (optional)
|
||||
|
||||
### TODO:
|
||||
|
||||
* Voice support
|
||||
* Unread indicators
|
||||
* User activities
|
||||
* Nicknames
|
||||
* More server management stuff
|
||||
* Manage friends
|
||||
* A bunch of other stuff
|
||||
|
||||
### Styling
|
||||
|
||||
#### CSS selectors
|
||||
|
||||
.app-window - Applied to all windows. This means the main window and all popups
|
||||
.app-popup - Additional class for `.app-window`s when the window is not the main window
|
||||
|
||||
.channel-list - Container of the channel list
|
||||
|
||||
.app-popup - Additional class for `.app-window`s when the window is not the main window
|
||||
|
||||
.channel-list - Container of the channel list
|
||||
.channel-row - All rows within the channel container
|
||||
.channel-row-channel - Only rows containing a channel
|
||||
.channel-row-category - Only rows containing a category
|
||||
.channel-row-guild - Only rows containing a guild
|
||||
.channel-row-label - All labels within the channel container
|
||||
.nsfw - Applied to channel row labels and their container for NSFW channels
|
||||
|
||||
.messages - Container of user messages
|
||||
.message-container - The container which holds a user's messages
|
||||
.message-container-author - The author label for a message container
|
||||
@@ -155,45 +100,47 @@ spam filter's wrath:
|
||||
.replying - Extra class for chat input container when a reply is currently being created
|
||||
.reaction-box - Contains a reaction image and the count
|
||||
.reacted - Additional class for reaction-box when the user has reacted with a particular reaction
|
||||
.reaction-count - Contains the count for reaction
|
||||
|
||||
.reaction-count - Contains the count for reaction
|
||||
|
||||
.completer - Container for the message completer
|
||||
.completer-entry - Container for a single entry in the completer
|
||||
.completer-entry-label - Contains the label for an entry in the completer
|
||||
.completer-entry-image - Contains the image for an entry in the completer
|
||||
|
||||
.completer-entry-image - Contains the image for an entry in the completer
|
||||
|
||||
.embed - Container for a message embed
|
||||
.embed-author - The author of an embed
|
||||
.embed-title - The title of an embed
|
||||
.embed-description - The description of an embed
|
||||
.embed-field-title - The title of an embed field
|
||||
.embed-field-value - The value of an embed field
|
||||
.embed-footer - The footer of an embed
|
||||
|
||||
.embed-footer - The footer of an embed
|
||||
|
||||
.members - Container of the member list
|
||||
.members-row - All rows within the members container
|
||||
.members-row-label - All labels in the members container
|
||||
.members-row-member - Rows containing a member
|
||||
.members-row-role - Rows containing a role
|
||||
.members-row-avatar - Contains the avatar for a row in the member list
|
||||
|
||||
.members-row-avatar - Contains the avatar for a row in the member list
|
||||
|
||||
.status-indicator - The status indicator
|
||||
.online - Applied to status indicators when the associated user is online
|
||||
.idle - Applied to status indicators when the associated user is away
|
||||
.dnd - Applied to status indicators when the associated user is on do not disturb
|
||||
.offline - Applied to status indicators when the associated user is offline
|
||||
|
||||
.typing-indicator - The typing indicator (also used for replies)
|
||||
|
||||
.offline - Applied to status indicators when the associated user is offline
|
||||
|
||||
.typing-indicator - The typing indicator (also used for replies)
|
||||
|
||||
Used in reorderable list implementation:
|
||||
.drag-icon .drag-hover-top .drag-hover-bottom
|
||||
.drag-icon
|
||||
.drag-hover-top
|
||||
.drag-hover-bottom
|
||||
|
||||
Used in guild settings popup:
|
||||
.guild-settings-window
|
||||
.guild-members-pane-list - Container for list of members in the members pane
|
||||
.guild-members-pane-info - Container for member info
|
||||
.guild-roles-pane-list - Container for list of roles in the roles pane
|
||||
|
||||
.guild-roles-pane-list - Container for list of roles in the roles pane
|
||||
|
||||
Used in profile popup:
|
||||
.mutual-friend-item - Applied to every item in the mutual friends list
|
||||
.mutual-friend-item-name - Name in mutual friend item
|
||||
@@ -218,62 +165,29 @@ Used in profile popup:
|
||||
.profile-switcher - Buttons used to switch viewed section of profile
|
||||
.profile-stack - Container for profile info that can be switched between
|
||||
.profile-badges - Container for badges
|
||||
.profile-badge
|
||||
|
||||
.profile-badge
|
||||
|
||||
### Settings
|
||||
|
||||
Settings are configured (for now) by editing abaddon.ini
|
||||
The format is similar to the standard Windows ini format **except**:
|
||||
|
||||
* `#` is used to begin comments as opposed to `;`
|
||||
* Section and key names are case-sensitive
|
||||
|
||||
You should edit these while the client is closed even though there's an option to reload while running
|
||||
This listing is organized by section.
|
||||
For example, memory_db would be set by adding `memory_db = true` under the line `[discord]`
|
||||
|
||||
#### discord
|
||||
|
||||
* 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
|
||||
* memory_db (true or false, default false) - if true, Discord data will be kept in memory as opposed to on disk
|
||||
* token (string) - Discord token used to login, this can be set from the menu
|
||||
* prefetch (true or false, default false) - if true, new messages will cause the avatar and image attachments to be
|
||||
automatically downloaded
|
||||
* prefetch (true or false, default false) - if true, new messages will cause the avatar and image attachments to be automatically downloaded
|
||||
|
||||
#### http
|
||||
|
||||
* user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
|
||||
* concurrent (int, default 20) - how many images can be concurrently retrieved
|
||||
* concurrent (int, default 10) - how many images can be concurrently retrieved
|
||||
|
||||
#### 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
|
||||
* animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used
|
||||
* owner_crown (true or false, default true) - show a crown next to the owner
|
||||
* unreads (true or false, default true) - show unread indicators and mention badges
|
||||
* save_state (true or false, default true) - save the state of the gui (active channels, tabs, expanded channels)
|
||||
* alt_menu (true or false, default false) - keep the menu hidden unless revealed with alt key
|
||||
* hide_to_tray (true or false, default false) - hide abaddon to the system tray on window close
|
||||
|
||||
#### 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
|
||||
* channelcolor (string) - color to use for SFW channels in the channel list
|
||||
* mentionbadgecolor (string) - background color for mention badges
|
||||
* mentionbadgetextcolor (string) - color to use for number displayed on mention badges
|
||||
* unreadcolor (string) - color to use for the unread indicator
|
||||
|
||||
### 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,3 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -12,23 +11,19 @@
|
||||
|
||||
#define APP_TITLE "Abaddon"
|
||||
|
||||
class AudioManager;
|
||||
|
||||
class Abaddon {
|
||||
private:
|
||||
Abaddon();
|
||||
|
||||
public:
|
||||
static Abaddon &Get();
|
||||
|
||||
~Abaddon();
|
||||
Abaddon(const Abaddon &) = delete;
|
||||
Abaddon &operator=(const Abaddon &) = delete;
|
||||
Abaddon(Abaddon &&) = delete;
|
||||
Abaddon &operator=(Abaddon &&) = delete;
|
||||
|
||||
int StartGTK();
|
||||
void OnShutdown();
|
||||
public:
|
||||
static Abaddon &Get();
|
||||
|
||||
int StartGTK();
|
||||
void StartDiscord();
|
||||
void StopDiscord();
|
||||
|
||||
@@ -38,9 +33,10 @@ public:
|
||||
void ActionDisconnect();
|
||||
void ActionSetToken();
|
||||
void ActionJoinGuildDialog();
|
||||
void ActionChannelOpened(Snowflake id, bool expand_to = true);
|
||||
void ActionChatInputSubmit(ChatSubmitParams data);
|
||||
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);
|
||||
@@ -51,26 +47,15 @@ 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);
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void ActionJoinVoiceChannel(Snowflake channel_id);
|
||||
void ActionDisconnectVoice();
|
||||
#endif
|
||||
|
||||
std::optional<Glib::ustring> ShowTextPrompt(const Glib::ustring &prompt, const Glib::ustring &title, const Glib::ustring &placeholder = "", Gtk::Window *window = nullptr);
|
||||
bool ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window = nullptr);
|
||||
|
||||
void ActionReloadSettings();
|
||||
void ActionReloadCSS();
|
||||
|
||||
ImageManager &GetImageManager();
|
||||
EmojiResource &GetEmojis();
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
AudioManager &GetAudio();
|
||||
#endif
|
||||
|
||||
std::string GetDiscordToken() const;
|
||||
bool IsDiscordActive() const;
|
||||
|
||||
@@ -81,44 +66,30 @@ 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);
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void OnVoiceConnected();
|
||||
void OnVoiceDisconnected();
|
||||
#endif
|
||||
|
||||
SettingsManager::Settings &GetSettings();
|
||||
const SettingsManager &GetSettings() const;
|
||||
|
||||
Glib::RefPtr<Gtk::CssProvider> GetStyleProvider();
|
||||
|
||||
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 GetStateCachePath();
|
||||
static std::string GetCSSPath(const std::string &path);
|
||||
static std::string GetResPath(const std::string &path);
|
||||
static std::string GetStateCachePath(const std::string &path);
|
||||
|
||||
protected:
|
||||
void RunFirstTimeDiscordStartup();
|
||||
|
||||
void ShowGuildVerificationGateDialog(Snowflake guild_id);
|
||||
|
||||
void CheckMessagesForMembers(const ChannelData &chan, const std::vector<Message> &msgs);
|
||||
|
||||
void SetupUserMenu();
|
||||
void SaveState();
|
||||
void LoadState();
|
||||
|
||||
void ManageHeapWindow(Gtk::Window *window);
|
||||
|
||||
Snowflake m_shown_user_menu_id;
|
||||
Snowflake m_shown_user_menu_guild_id;
|
||||
@@ -133,8 +104,6 @@ protected:
|
||||
Gtk::MenuItem *m_user_menu_roles;
|
||||
Gtk::MenuItem *m_user_menu_remove_recipient;
|
||||
Gtk::Menu *m_user_menu_roles_submenu;
|
||||
Gtk::Menu *m_tray_menu;
|
||||
Gtk::MenuItem *m_tray_exit;
|
||||
|
||||
void on_user_menu_insert_mention();
|
||||
void on_user_menu_ban();
|
||||
@@ -142,10 +111,6 @@ protected:
|
||||
void on_user_menu_copy_id();
|
||||
void on_user_menu_open_dm();
|
||||
void on_user_menu_remove_recipient();
|
||||
void on_tray_click();
|
||||
void on_tray_popup_menu(int button, int activate_time);
|
||||
void on_tray_menu_click();
|
||||
void on_window_hide();
|
||||
|
||||
private:
|
||||
SettingsManager m_settings;
|
||||
@@ -160,15 +125,8 @@ private:
|
||||
ImageManager m_img_mgr;
|
||||
EmojiResource m_emojis;
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
std::unique_ptr<AudioManager> m_audio;
|
||||
Gtk::Window *m_voice_window = nullptr;
|
||||
#endif
|
||||
|
||||
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
|
||||
Glib::RefPtr<Gtk::StatusIcon> m_tray;
|
||||
std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
/bin/gdbus.exe
|
||||
/bin/gspawn-win64-helper-console.exe
|
||||
/bin/libatk-1.0-0.dll
|
||||
/bin/libatkmm-1.6-1.dll
|
||||
/bin/libbrotlicommon.dll
|
||||
/bin/libbrotlidec.dll
|
||||
/bin/libbz2-1.dll
|
||||
/bin/libcairo-2.dll
|
||||
/bin/libcairo-gobject-2.dll
|
||||
/bin/libcairomm-1.0-1.dll
|
||||
/bin/libcrypto-1_1-x64.dll
|
||||
/bin/libcurl-4.dll
|
||||
/bin/libdatrie-1.dll
|
||||
/bin/libdeflate.dll
|
||||
/bin/libepoxy-0.dll
|
||||
/bin/libexpat-1.dll
|
||||
/bin/libffi-8.dll
|
||||
/bin/libfontconfig-1.dll
|
||||
/bin/libfreetype-6.dll
|
||||
/bin/libfribidi-0.dll
|
||||
/bin/libgcc_s_seh-1.dll
|
||||
/bin/libgdk-3-0.dll
|
||||
/bin/libgdk_pixbuf-2.0-0.dll
|
||||
/bin/libgdkmm-3.0-1.dll
|
||||
/bin/libgio-2.0-0.dll
|
||||
/bin/libgiomm-2.4-1.dll
|
||||
/bin/libglib-2.0-0.dll
|
||||
/bin/libglibmm-2.4-1.dll
|
||||
/bin/libgmodule-2.0-0.dll
|
||||
/bin/libgobject-2.0-0.dll
|
||||
/bin/libgraphite2.dll
|
||||
/bin/libgtk-3-0.dll
|
||||
/bin/libgtkmm-3.0-1.dll
|
||||
/bin/libhandy-1-0.dll
|
||||
/bin/libharfbuzz-0.dll
|
||||
/bin/libiconv-2.dll
|
||||
/bin/libidn2-0.dll
|
||||
/bin/libintl-8.dll
|
||||
/bin/libnghttp2-14.dll
|
||||
/bin/libpango-1.0-0.dll
|
||||
/bin/libpangocairo-1.0-0.dll
|
||||
/bin/libpangoft2-1.0-0.dll
|
||||
/bin/libpangomm-1.4-1.dll
|
||||
/bin/libpangowin32-1.0-0.dll
|
||||
/bin/libpcre2-8-0.dll
|
||||
/bin/libpixman-1-0.dll
|
||||
/bin/libpng16-16.dll
|
||||
/bin/libpsl-5.dll
|
||||
/bin/libsigc-2.0-0.dll
|
||||
/bin/libspdlog.dll
|
||||
/bin/libsqlite3-0.dll
|
||||
/bin/libssh2-1.dll
|
||||
/bin/libssl-1_1-x64.dll
|
||||
/bin/libstdc++-6.dll
|
||||
/bin/libthai-0.dll
|
||||
/bin/libunistring-2.dll
|
||||
/bin/libwinpthread-1.dll
|
||||
/bin/libzstd.dll
|
||||
/bin/zlib1.dll
|
||||
/../usr/bin/msys-2.0.dll
|
||||
@@ -1 +0,0 @@
|
||||
document-send-symbolic
|
||||
1
ci/vcpkg
Submodule
1
ci/vcpkg
Submodule
Submodule ci/vcpkg added at 50ea8c0ab7
@@ -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
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
set(IXWebSocket_LIBRARY_NAME ixwebsocket)
|
||||
|
||||
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_LIBRARY_NAME}
|
||||
PATH_SUFFIXES ${IXWebSocket_LIBRARY_NAME}
|
||||
${IXWebSocket_LIBRARY_NAME}/include)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(IXWebSocket
|
||||
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()
|
||||
@@ -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
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_gdk QUIET gdk-3.0)
|
||||
set(gdk_DEFINITIONS ${PC_gdk_CFLAGS_OTHER})
|
||||
endif ()
|
||||
|
||||
set(gdk_INCLUDE_HINTS ${PC_gdk_INCLUDEDIR} ${PC_gdk_INCLUDE_DIRS})
|
||||
set(gdk_LIBRARY_HINTS ${PC_gdk_LIBDIR} ${PC_gdk_LIBRARY_DIRS})
|
||||
|
||||
find_path(gdk_INCLUDE_DIR
|
||||
NAMES gdk/gdk.h
|
||||
HINTS ${gdk_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES gdk-3.0)
|
||||
|
||||
find_library(gdk_LIBRARY
|
||||
NAMES gdk-3.0
|
||||
gdk-3
|
||||
gdk
|
||||
HINTS ${gdk_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib)
|
||||
|
||||
set(gdk_LIBRARIES ${gdk_LIBRARY})
|
||||
set(gdk_INCLUDE_DIRS ${gdk_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdk
|
||||
REQUIRED_VARS
|
||||
gdk_LIBRARY
|
||||
gdk_INCLUDE_DIR
|
||||
VERSION_VAR gdk_VERSION)
|
||||
|
||||
mark_as_advanced(gdk_INCLUDE_DIR gdk_LIBRARY)
|
||||
@@ -1,50 +1,49 @@
|
||||
set(gdkmm_LIBRARY_NAME gdkmm-3.0)
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PKGCONFIG_gdkmm QUIET ${gdkmm_LIBRARY_NAME})
|
||||
set(gdkmm_DEFINITIONS ${PKGCONFIG_gdkmm_CFLAGS_OTHER})
|
||||
pkg_check_modules(PKGCONFIG_GDKMM QUIET ${GDKMM_LIBRARY_NAME})
|
||||
set(GDKMM_DEFINITIONS ${PKGCONFIG_GDKMM_CFLAGS_OTHER})
|
||||
endif (PKG_CONFIG_FOUND)
|
||||
|
||||
set(gdkmm_INCLUDE_HINTS ${PKGCONFIG_gdkmm_INCLUDEDIR} ${PKGCONFIG_gdkmm_INCLUDE_DIRS})
|
||||
set(gdkmm_LIBRARY_HINTS ${PKGCONFIG_gdkmm_LIBDIR} ${PKGCONFIG_gdkmm_LIBRARY_DIRS})
|
||||
set(GDKMM_INCLUDE_HINTS ${PKGCONFIG_GDKMM_INCLUDEDIR} ${PKGCONFIG_GDKMM_INCLUDE_DIRS})
|
||||
set(GDKMM_LIBRARY_HINTS ${PKGCONFIG_GDKMM_LIBDIR} ${PKGCONFIG_GDKMM_LIBRARY_DIRS})
|
||||
|
||||
find_path(gdkmm_INCLUDE_DIR
|
||||
find_path(GDKMM_INCLUDE_DIR
|
||||
NAMES gdkmm.h
|
||||
HINTS ${gdkmm_INCLUDE_HINTS}
|
||||
HINTS ${GDKMM_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME})
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME})
|
||||
|
||||
find_path(gdkmm_CONFIG_INCLUDE_DIR
|
||||
find_path(GDKMM_CONFIG_INCLUDE_DIR
|
||||
NAMES gdkmmconfig.h
|
||||
HINTS ${gdkmm_LIBRARY_HINTS}
|
||||
HINTS ${GDKMM_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}/include)
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}/include)
|
||||
|
||||
find_library(gdkmm_LIBRARY
|
||||
NAMES ${gdkmm_LIBRARY_NAME}
|
||||
gdkmm
|
||||
HINTS ${gdkmm_LIBRARY_HINTS}
|
||||
find_library(GDKMM_LIBRARY
|
||||
NAMES ${GDKMM_LIBRARY_NAME}
|
||||
gdkmm
|
||||
HINTS ${GDKMM_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${gdkmm_LIBRARY_NAME}
|
||||
${gdkmm_LIBRARY_NAME}/include)
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}
|
||||
${GDKMM_LIBRARY_NAME}/include)
|
||||
|
||||
find_package(gdk)
|
||||
|
||||
set(gdkmm_LIBRARIES ${gdkmm_LIBRARY};${gdk_LIBRARIES})
|
||||
set(gdkmm_INCLUDE_DIRS ${gdkmm_INCLUDE_DIR};${gdkmm_CONFIG_INCLUDE_DIRS};${gdkmm_CONFIG_INCLUDE_DIR};${gdk_INCLUDE_DIRS})
|
||||
set(GDKMM_LIBRARIES ${GDKMM_LIBRARY})
|
||||
set(GDKMM_INCLUDE_DIRS ${GDKMM_INCLUDE_DIR};${GDKMM_CONFIG_INCLUDE_DIRS};${GDKMM_CONFIG_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gdkmm
|
||||
FOUND_VAR GDKMM_FOUND
|
||||
REQUIRED_VARS
|
||||
gdkmm_LIBRARY
|
||||
gdkmm_INCLUDE_DIRS
|
||||
VERSION_VAR gdkmm_VERSION)
|
||||
GDKMM_LIBRARY
|
||||
GDKMM_INCLUDE_DIRS
|
||||
VERSION_VAR GDKMM_VERSION)
|
||||
|
||||
mark_as_advanced(gdkmm_INCLUDE_DIR gdkmm_LIBRARY)
|
||||
mark_as_advanced(GDKMM_INCLUDE_DIR GDKMM_LIBRARY)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,70 +2,57 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(PC_GLIB2 QUIET glib-2.0)
|
||||
|
||||
find_path(GLIB_INCLUDE_DIR
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
NAMES glib.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIR})
|
||||
|
||||
find_library(GLIB_LIBRARIES
|
||||
NAMES glib2
|
||||
glib-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
$ENV{GLIB2_HOME}/lib
|
||||
$ENV{GLIB2_ROOT}/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/lib
|
||||
/glib-2.0/lib
|
||||
PATH_SUFFIXES glib2 glib-2.0
|
||||
)
|
||||
|
||||
find_library(glib_GOBJECT_LIBRARIES
|
||||
NAMES gobject-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
find_library(glib_GIO_LIBRARIES
|
||||
NAMES gio-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
)
|
||||
NAMES glib2
|
||||
glib-2.0
|
||||
HINTS ${PC_GLIB2_LIBDIR}
|
||||
${PC_GLIB2_LIBRARY_DIRS}
|
||||
$ENV{GLIB2_HOME}/lib
|
||||
$ENV{GLIB2_ROOT}/lib
|
||||
/usr/local/lib
|
||||
/usr/lib
|
||||
/lib
|
||||
/glib-2.0/lib
|
||||
PATH_SUFFIXES glib2 glib-2.0
|
||||
)
|
||||
|
||||
get_filename_component(_GLIB2_LIB_DIR "${GLIB_LIBRARIES}" PATH)
|
||||
find_path(GLIB_CONFIG_INCLUDE_DIR
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
${_GLIB2_LIB_DIR}
|
||||
${CMAKE_SYSTEM_LIBRARY_PATH}
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
NAMES glibconfig.h
|
||||
HINTS ${PC_GLIB2_INCLUDEDIR}
|
||||
${PC_GLIB2_INCLUDE_DIRS}
|
||||
$ENV{GLIB2_HOME}/include
|
||||
$ENV{GLIB2_ROOT}/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
/glib2/include
|
||||
/glib-2.0/include
|
||||
${_GLIB2_LIB_DIR}
|
||||
${CMAKE_SYSTEM_LIBRARY_PATH}
|
||||
PATH_SUFFIXES glib2 glib-2.0 glib-2.0/include
|
||||
)
|
||||
if (GLIB_CONFIG_INCLUDE_DIR)
|
||||
set(GLIB_INCLUDE_DIRS ${GLIB_INCLUDE_DIRS} ${GLIB_CONFIG_INCLUDE_DIR})
|
||||
endif ()
|
||||
|
||||
set(GLIB_LIBRARIES ${GLIB_LIBRARIES} ${glib_GOBJECT_LIBRARIES} ${glib_GIO_LIBRARIES})
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(glib
|
||||
REQUIRED_VARS
|
||||
GLIB_LIBRARIES
|
||||
GLIB_INCLUDE_DIRS
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR glib_GOBJECT_LIBRARIES)
|
||||
FOUND_VAR GLIB_FOUND
|
||||
REQUIRED_VARS
|
||||
GLIB_LIBRARIES
|
||||
GLIB_INCLUDE_DIRS
|
||||
VERSION_VAR GLIB_VERSION)
|
||||
mark_as_advanced(GLIB_INCLUDE_DIR GLIB_CONFIG_INCLUDE_DIR)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
set(GTKMM_LIBRARY_NAME gtkmm-3.0)
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
set(GTKMM_LIBRARY_NAME gtkmm-3.0)
|
||||
set(GDKMM_LIBRARY_NAME gdkmm-3.0)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_GTKMM QUIET ${GTKMM_LIBRARY_NAME})
|
||||
pkg_check_modules(PC_GDKMM QUIET ${GDKMM_LIBRARY_NAME})
|
||||
pkg_check_modules(PC_PANGOMM QUIET ${PANGOMM_LIBRARY_NAME})
|
||||
set(GTKMM_DEFINITIONS ${PC_GTKMM_CFLAGS_OTHER})
|
||||
endif ()
|
||||
set(GTKMM_DEFINITIONS ${PC_GTKMM_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
find_package(gtk)
|
||||
find_package(glibmm)
|
||||
@@ -46,14 +46,15 @@ find_path(GDKMM_CONFIG_INCLUDE_DIR
|
||||
HINTS ${GDKMM_INCLUDE_HINTS}
|
||||
PATH_SUFFIXES ${GDKMM_LIBRARY_NAME}/include)
|
||||
|
||||
set(GTKMM_LIBRARIES ${GTKMM_LIB};${gdkmm_LIBRARIES};${GTK_LIBRARIES};${GLIBMM_LIBRARIES};${PANGOMM_LIBRARIES};${CAIROMM_LIBRARIES};${ATKMM_LIBRARIES};${SIGC++_LIBRARIES})
|
||||
set(GTKMM_INCLUDE_DIRS ${GTKMM_INCLUDE_DIR};${GTKMM_CONFIG_INCLUDE_DIR};${gdkmm_INCLUDE_DIRS};${gdkmm_CONFIG_INCLUDE_DIR};${GTK_INCLUDE_DIRS};${GLIBMM_INCLUDE_DIRS};${PANGOMM_INCLUDE_DIRS};${CAIROMM_INCLUDE_DIRS};${ATKMM_INCLUDE_DIRS};${SIGC++_INCLUDE_DIRS})
|
||||
set(GTKMM_LIBRARIES ${GTKMM_LIB};${GDKMM_LIBRARY};${GTK_LIBRARIES};${GLIBMM_LIBRARIES};${PANGOMM_LIBRARIES};${CAIROMM_LIBRARIES};${ATKMM_LIBRARIES};${SIGC++_LIBRARIES})
|
||||
set(GTKMM_INCLUDE_DIRS ${GTKMM_INCLUDE_DIR};${GTKMM_CONFIG_INCLUDE_DIR};${GDKMM_INCLUDE_DIR};${GDKMM_CONFIG_INCLUDE_DIR};${GTK_INCLUDE_DIRS};${GLIBMM_INCLUDE_DIRS};${PANGOMM_INCLUDE_DIRS};${CAIROMM_INCLUDE_DIRS};${ATKMM_INCLUDE_DIRS};${SIGC++_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(gtkmm
|
||||
FOUND_VAR GTKMM_FOUND
|
||||
REQUIRED_VARS
|
||||
GTKMM_LIB
|
||||
GTKMM_INCLUDE_DIRS
|
||||
GTKMM_LIB
|
||||
GTKMM_INCLUDE_DIRS
|
||||
VERSION_VAR GTKMM_VERSION)
|
||||
|
||||
mark_as_advanced(GTKMM_INCLUDE_DIR GTKMM_LIBRARY)
|
||||
|
||||
17
cmake/Findixwebsocket.cmake
Normal file
17
cmake/Findixwebsocket.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
find_path(IXWEBSOCKET_INCLUDE_DIR
|
||||
NAMES ixwebsocket/IXWebSocket.h)
|
||||
|
||||
find_library(IXWEBSOCKET_LIBRARY
|
||||
NAMES ixwebsocket
|
||||
HINTS ${IXWEBSOCKET_LIBRARY_ROOT})
|
||||
|
||||
set(IXWEBSOCKET_LIBRARIES ${IXWEBSOCKET_LIBRARY})
|
||||
set(IXWEBSOCKET_INCLUDE_DIRS ${IXWEBSOCKET_INCLUDE_DIR})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(ixwebsocket
|
||||
FOUND_VAR IXWEBSOCKET_FOUND
|
||||
REQUIRED_VARS
|
||||
IXWEBSOCKET_LIBRARY
|
||||
IXWEBSOCKET_INCLUDE_DIR
|
||||
VERSION_VAR IXWEBSOCKET_VERSION)
|
||||
@@ -1,39 +0,0 @@
|
||||
set(libhandy_LIBRARY_NAME libhandy-1)
|
||||
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_libhandy QUIET ${libhandy_LIBRARY_NAME})
|
||||
set(libhandy_DEfINITIONS ${PC_libhandy_CFLAGS_OTHER})
|
||||
endif (PKG_CONFIG_FOUND)
|
||||
|
||||
set(libhandy_INCLUDE_HINTS ${PC_libhandy_INCLUDEDIR} ${PC_libhandy_INCLUDE_DIRS})
|
||||
set(libhandy_LIBRARY_HINTS ${PC_libhandy_LIBDIR} ${PC_libhandy_LIBRARY_DIRS})
|
||||
|
||||
find_path(libhandy_INCLUDE_DIR
|
||||
NAMES handy.h
|
||||
HINTS ${libhandy_INCLUDE_HINTS}
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
/opt/local/include
|
||||
PATH_SUFFIXES ${libhandy_LIBRARY_NAME})
|
||||
|
||||
find_library(libhandy_LIBRARY
|
||||
NAMES ${libhandy_LIBRARY_NAME} handy-1
|
||||
HINTS ${libhandy_LIBRARY_HINTS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
/opt/local/lib
|
||||
PATH_SUFFIXES ${libhandy_LIBRARY_NAME}
|
||||
${libhandy_LIBRARY_NAME}/include)
|
||||
|
||||
set(libhandy_LIBRARIES ${libhandy_LIBRARY})
|
||||
set(libhandy_INCLUDE_DIRS ${libhandy_INCLUDE_DIR};${libhandy_CONFIG_INCLUDE_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(libhandy
|
||||
REQUIRED_VARS
|
||||
libhandy_LIBRARY
|
||||
libhandy_INCLUDE_DIR
|
||||
VERSION_VAR libhandy_VERSION)
|
||||
|
||||
mark_as_advanced(libhandy_INCLUDE_DIR libhandy_LIBRARY)
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,8 @@ CellRendererPixbufAnimation::CellRendererPixbufAnimation()
|
||||
property_ypad() = 2;
|
||||
}
|
||||
|
||||
CellRendererPixbufAnimation::~CellRendererPixbufAnimation() {}
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> CellRendererPixbufAnimation::property_pixbuf() {
|
||||
return m_property_pixbuf.get_proxy();
|
||||
}
|
||||
@@ -59,6 +61,7 @@ void CellRendererPixbufAnimation::render_vfunc(const Cairo::RefPtr<Cairo::Contex
|
||||
Gtk::CellRendererState flags) {
|
||||
Gtk::Requisition minimum, natural;
|
||||
get_preferred_size(widget, minimum, natural);
|
||||
auto alloc = widget.get_allocation();
|
||||
int xpad, ypad;
|
||||
get_padding(xpad, ypad);
|
||||
int pix_x = cell_area.get_x() + xpad;
|
||||
@@ -6,7 +6,7 @@
|
||||
class CellRendererPixbufAnimation : public Gtk::CellRenderer {
|
||||
public:
|
||||
CellRendererPixbufAnimation();
|
||||
~CellRendererPixbufAnimation() override = default;
|
||||
virtual ~CellRendererPixbufAnimation();
|
||||
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_pixbuf();
|
||||
Glib::PropertyProxy<Glib::RefPtr<Gdk::PixbufAnimation>> property_pixbuf_animation();
|
||||
813
components/channels.cpp
Normal file
813
components/channels.cpp
Normal file
@@ -0,0 +1,813 @@
|
||||
#include "channels.hpp"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include "../abaddon.hpp"
|
||||
#include "../imgmanager.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
void ChannelListRow::Collapse() {}
|
||||
|
||||
void ChannelListRow::Expand() {}
|
||||
|
||||
void ChannelListRow::MakeReadOnly(Gtk::TextView *tv) {
|
||||
tv->set_can_focus(false);
|
||||
tv->set_editable(false);
|
||||
tv->signal_realize().connect([tv]() {
|
||||
auto window = tv->get_window(Gtk::TEXT_WINDOW_TEXT);
|
||||
auto display = window->get_display();
|
||||
auto cursor = Gdk::Cursor::create(display, "default"); // textview uses "text" which looks out of place
|
||||
window->set_cursor(cursor);
|
||||
});
|
||||
// stupid hack to prevent selection
|
||||
auto buf = tv->get_buffer();
|
||||
buf->property_has_selection().signal_changed().connect([tv, buf]() {
|
||||
Gtk::TextBuffer::iterator a, b;
|
||||
buf->get_bounds(a, b);
|
||||
buf->select_range(a, a);
|
||||
});
|
||||
}
|
||||
|
||||
ChannelListRowDMHeader::ChannelListRowDMHeader() {
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
m_lbl = Gtk::manage(new Gtk::Label);
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_lbl->set_use_markup(true);
|
||||
m_lbl->set_markup("<b>Direct Messages</b>");
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_lbl);
|
||||
|
||||
m_ev->add(*m_box);
|
||||
add(*m_ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
ChannelListRowDMChannel::ChannelListRowDMChannel(const ChannelData *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
|
||||
std::optional<UserData> top_recipient; // potentially nullopt in group dm
|
||||
const auto recipients = data->GetDMRecipients();
|
||||
if (recipients.size() > 0)
|
||||
top_recipient = recipients[0];
|
||||
|
||||
const static bool alt = Abaddon::Get().GetSettings().GetUseMobileLayout();
|
||||
if (alt) {
|
||||
auto *tmp = Gtk::manage(new Gtk::Label);
|
||||
m_lbl = tmp;
|
||||
if (data->Type == ChannelType::DM)
|
||||
tmp->set_text(top_recipient->Username);
|
||||
else if (data->Type == ChannelType::GROUP_DM)
|
||||
tmp->set_text(std::to_string(recipients.size()) + " users");
|
||||
} else {
|
||||
auto *tmp = Gtk::manage(new Gtk::TextView);
|
||||
m_lbl = tmp;
|
||||
MakeReadOnly(tmp);
|
||||
|
||||
auto buf = tmp->get_buffer();
|
||||
if (data->Type == ChannelType::DM)
|
||||
buf->set_text(top_recipient->Username);
|
||||
else if (data->Type == ChannelType::GROUP_DM)
|
||||
buf->set_text(std::to_string(recipients.size()) + " users");
|
||||
|
||||
static bool show_emojis = Abaddon::Get().GetSettings().GetShowEmojis();
|
||||
if (show_emojis)
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
}
|
||||
|
||||
AddWidgetMenuHandler(m_ev, m_menu);
|
||||
AddWidgetMenuHandler(m_lbl, m_menu);
|
||||
|
||||
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
|
||||
m_menu_copy_id->signal_activate().connect([this] {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(ID));
|
||||
});
|
||||
|
||||
if (data->Type == ChannelType::GROUP_DM)
|
||||
m_menu_close = Gtk::manage(new Gtk::MenuItem("_Leave DM", true));
|
||||
else
|
||||
m_menu_close = Gtk::manage(new Gtk::MenuItem("_Close DM", true));
|
||||
m_menu_close->signal_activate().connect([this] {
|
||||
Abaddon::Get().GetDiscordClient().CloseDM(ID);
|
||||
});
|
||||
|
||||
m_menu.append(*m_menu_copy_id);
|
||||
m_menu.append(*m_menu_close);
|
||||
m_menu.show_all();
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
if (data->Type == ChannelType::DM) {
|
||||
m_status = Gtk::manage(new StatusIndicator(top_recipient->ID));
|
||||
m_status->set_margin_start(5);
|
||||
|
||||
m_icon = Gtk::manage(new Gtk::Image(Abaddon::Get().GetImageManager().GetPlaceholder(24)));
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_icon->property_pixbuf() = pb->scale_simple(24, 24, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
Abaddon::Get().GetImageManager().LoadFromURL(top_recipient->GetAvatarURL("png", "16"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
if (m_icon != nullptr)
|
||||
m_box->pack_start(*m_icon);
|
||||
if (m_status != nullptr)
|
||||
m_box->pack_start(*m_status);
|
||||
m_box->pack_start(*m_lbl);
|
||||
m_ev->add(*m_box);
|
||||
add(*m_ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
ChannelListRowGuild::ChannelListRowGuild(const GuildData *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
|
||||
const static bool alt = Abaddon::Get().GetSettings().GetUseMobileLayout();
|
||||
if (alt) {
|
||||
m_lbl = Gtk::manage(new Gtk::Label(data->Name));
|
||||
} else {
|
||||
auto *tmp = Gtk::manage(new Gtk::TextView);
|
||||
m_lbl = tmp;
|
||||
MakeReadOnly(tmp);
|
||||
auto buf = tmp->get_buffer();
|
||||
Gtk::TextBuffer::iterator start, end;
|
||||
buf->get_bounds(start, end);
|
||||
buf->insert_markup(start, "<b>" + Glib::Markup::escape_text(data->Name) + "</b>");
|
||||
static bool show_emojis = Abaddon::Get().GetSettings().GetShowEmojis();
|
||||
if (show_emojis)
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
}
|
||||
|
||||
AddWidgetMenuHandler(m_ev, m_menu);
|
||||
AddWidgetMenuHandler(m_lbl, m_menu);
|
||||
|
||||
m_menu_copyid = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
|
||||
m_menu_copyid->signal_activate().connect([this]() {
|
||||
m_signal_copy_id.emit();
|
||||
});
|
||||
m_menu.append(*m_menu_copyid);
|
||||
|
||||
m_menu_leave = Gtk::manage(new Gtk::MenuItem("_Leave Guild", true));
|
||||
m_menu_leave->signal_activate().connect([this]() {
|
||||
m_signal_leave.emit();
|
||||
});
|
||||
m_menu.append(*m_menu_leave);
|
||||
|
||||
m_menu_settings = Gtk::manage(new Gtk::MenuItem("Guild _Settings", true));
|
||||
m_menu_settings->signal_activate().connect([this]() {
|
||||
m_signal_settings.emit();
|
||||
});
|
||||
m_menu.append(*m_menu_settings);
|
||||
|
||||
m_menu.show_all();
|
||||
|
||||
const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
if (data->HasIcon()) {
|
||||
if (data->HasAnimatedIcon() && show_animations) {
|
||||
m_icon = Gtk::manage(new Gtk::Image(img.GetPlaceholder(24)));
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_icon->property_pixbuf_animation() = pb;
|
||||
};
|
||||
img.LoadAnimationFromURL(data->GetIconURL("gif", "32"), 24, 24, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
m_icon = Gtk::manage(new Gtk::Image(img.GetPlaceholder(24)));
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_icon->property_pixbuf() = pb->scale_simple(24, 24, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
img.LoadFromURL(data->GetIconURL("png", "32"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
} else {
|
||||
m_icon = Gtk::manage(new Gtk::Image(Abaddon::Get().GetImageManager().GetPlaceholder(24)));
|
||||
}
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
get_style_context()->add_class("channel-row-guild");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_icon);
|
||||
m_box->pack_start(*m_lbl);
|
||||
m_ev->add(*m_box);
|
||||
add(*m_ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
ChannelListRowGuild::type_signal_copy_id ChannelListRowGuild::signal_copy_id() {
|
||||
return m_signal_copy_id;
|
||||
}
|
||||
|
||||
ChannelListRowGuild::type_signal_leave ChannelListRowGuild::signal_leave() {
|
||||
return m_signal_leave;
|
||||
}
|
||||
|
||||
ChannelListRowGuild::type_signal_settings ChannelListRowGuild::signal_settings() {
|
||||
return m_signal_settings;
|
||||
}
|
||||
|
||||
ChannelListRowCategory::ChannelListRowCategory(const ChannelData *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
|
||||
const static bool alt = Abaddon::Get().GetSettings().GetUseMobileLayout();
|
||||
if (alt) {
|
||||
m_lbl = Gtk::manage(new Gtk::Label(*data->Name));
|
||||
} else {
|
||||
auto *tmp = Gtk::manage(new Gtk::TextView);
|
||||
m_lbl = tmp;
|
||||
MakeReadOnly(tmp);
|
||||
auto buf = tmp->get_buffer();
|
||||
buf->set_text(*data->Name);
|
||||
static bool show_emojis = Abaddon::Get().GetSettings().GetShowEmojis();
|
||||
if (show_emojis)
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
}
|
||||
m_arrow = Gtk::manage(new Gtk::Arrow(Gtk::ARROW_DOWN, Gtk::SHADOW_NONE));
|
||||
|
||||
m_menu_copyid = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
|
||||
m_menu_copyid->signal_activate().connect([this]() {
|
||||
m_signal_copy_id.emit();
|
||||
});
|
||||
m_menu.append(*m_menu_copyid);
|
||||
|
||||
m_menu.show_all();
|
||||
|
||||
AddWidgetMenuHandler(m_ev, m_menu);
|
||||
AddWidgetMenuHandler(m_lbl, m_menu);
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
get_style_context()->add_class("channel-row-category");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_arrow);
|
||||
m_box->pack_start(*m_lbl);
|
||||
m_ev->add(*m_box);
|
||||
add(*m_ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ChannelListRowCategory::Collapse() {
|
||||
m_arrow->set(Gtk::ARROW_RIGHT, Gtk::SHADOW_NONE);
|
||||
}
|
||||
|
||||
void ChannelListRowCategory::Expand() {
|
||||
m_arrow->set(IsUserCollapsed ? Gtk::ARROW_RIGHT : Gtk::ARROW_DOWN, Gtk::SHADOW_NONE);
|
||||
}
|
||||
|
||||
ChannelListRowCategory::type_signal_copy_id ChannelListRowCategory::signal_copy_id() {
|
||||
return m_signal_copy_id;
|
||||
}
|
||||
|
||||
ChannelListRowChannel::ChannelListRowChannel(const ChannelData *data) {
|
||||
ID = data->ID;
|
||||
m_ev = Gtk::manage(new Gtk::EventBox);
|
||||
m_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
|
||||
const static bool alt = Abaddon::Get().GetSettings().GetUseMobileLayout();
|
||||
if (alt) {
|
||||
m_lbl = Gtk::manage(new Gtk::Label("#" + *data->Name));
|
||||
} else {
|
||||
auto *tmp = Gtk::manage(new Gtk::TextView);
|
||||
m_lbl = tmp;
|
||||
MakeReadOnly(tmp);
|
||||
auto buf = tmp->get_buffer();
|
||||
buf->set_text("#" + *data->Name);
|
||||
static bool show_emojis = Abaddon::Get().GetSettings().GetShowEmojis();
|
||||
if (show_emojis)
|
||||
Abaddon::Get().GetEmojis().ReplaceEmojis(buf, ChannelEmojiSize);
|
||||
}
|
||||
|
||||
m_menu_copyid = Gtk::manage(new Gtk::MenuItem("_Copy ID", true));
|
||||
m_menu_copyid->signal_activate().connect([this]() {
|
||||
m_signal_copy_id.emit();
|
||||
});
|
||||
m_menu.append(*m_menu_copyid);
|
||||
|
||||
m_menu.show_all();
|
||||
|
||||
AddWidgetMenuHandler(m_ev, m_menu);
|
||||
AddWidgetMenuHandler(m_lbl, m_menu);
|
||||
|
||||
get_style_context()->add_class("channel-row");
|
||||
get_style_context()->add_class("channel-row-channel");
|
||||
m_lbl->get_style_context()->add_class("channel-row-label");
|
||||
|
||||
if (data->IsNSFW.has_value() && *data->IsNSFW) {
|
||||
get_style_context()->add_class("nsfw");
|
||||
m_lbl->get_style_context()->add_class("nsfw");
|
||||
}
|
||||
|
||||
m_box->set_halign(Gtk::ALIGN_START);
|
||||
m_box->pack_start(*m_lbl);
|
||||
m_ev->add(*m_box);
|
||||
add(*m_ev);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
ChannelListRowChannel::type_signal_copy_id ChannelListRowChannel::signal_copy_id() {
|
||||
return m_signal_copy_id;
|
||||
}
|
||||
|
||||
ChannelList::ChannelList() {
|
||||
m_main = Gtk::manage(new Gtk::ScrolledWindow);
|
||||
m_list = Gtk::manage(new Gtk::ListBox);
|
||||
|
||||
m_list->get_style_context()->add_class("channel-list");
|
||||
|
||||
m_list->set_activate_on_single_click(true);
|
||||
m_list->signal_row_activated().connect(sigc::mem_fun(*this, &ChannelList::on_row_activated));
|
||||
|
||||
m_main->add(*m_list);
|
||||
m_main->show_all();
|
||||
|
||||
// maybe will regret doing it this way
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto cb = [this, &discord](const Message &message) {
|
||||
const auto channel = discord.GetChannel(message.ChannelID);
|
||||
if (!channel.has_value()) return;
|
||||
if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM)
|
||||
CheckBumpDM(message.ChannelID);
|
||||
};
|
||||
discord.signal_message_create().connect(sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
Gtk::Widget *ChannelList::GetRoot() const {
|
||||
return m_main;
|
||||
}
|
||||
|
||||
void ChannelList::UpdateNewGuild(Snowflake id) {
|
||||
auto sort = Abaddon::Get().GetDiscordClient().GetUserSortedGuilds();
|
||||
if (sort.size() == 1) {
|
||||
UpdateListing();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto insert_at = [this, id](int listpos) {
|
||||
InsertGuildAt(id, listpos);
|
||||
};
|
||||
|
||||
auto it = std::find(sort.begin(), sort.end(), id);
|
||||
if (it == sort.end()) return;
|
||||
// if the new guild pos is at the end use -1
|
||||
if (it + 1 == sort.end()) {
|
||||
insert_at(-1);
|
||||
return;
|
||||
}
|
||||
// find the position of the guild below it into the listbox
|
||||
auto below_id = *(it + 1);
|
||||
auto below_it = m_id_to_row.find(below_id);
|
||||
if (below_it == m_id_to_row.end()) {
|
||||
UpdateListing();
|
||||
return;
|
||||
}
|
||||
auto below_pos = below_it->second->get_index();
|
||||
// stick it just above
|
||||
insert_at(below_pos - 1);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateRemoveGuild(Snowflake id) {
|
||||
auto it = m_guild_id_to_row.find(id);
|
||||
if (it == m_guild_id_to_row.end()) return;
|
||||
auto row = dynamic_cast<ChannelListRow *>(it->second);
|
||||
if (row == nullptr) return;
|
||||
DeleteRow(row);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateRemoveChannel(Snowflake id) {
|
||||
auto it = m_id_to_row.find(id);
|
||||
if (it == m_id_to_row.end()) return;
|
||||
auto row = dynamic_cast<ChannelListRow *>(it->second);
|
||||
if (row == nullptr) return;
|
||||
DeleteRow(row);
|
||||
}
|
||||
|
||||
// this is total shit
|
||||
void ChannelList::UpdateChannelCategory(Snowflake id) {
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*data->GuildID);
|
||||
auto git = m_guild_id_to_row.find(*data->GuildID);
|
||||
if (git == m_guild_id_to_row.end()) return;
|
||||
auto *guild_row = git->second;
|
||||
if (!data.has_value() || !guild.has_value()) return;
|
||||
auto it = m_id_to_row.find(id);
|
||||
if (it == m_id_to_row.end()) return;
|
||||
auto row = dynamic_cast<ChannelListRowCategory *>(it->second);
|
||||
if (row == nullptr) return;
|
||||
const bool old_collapsed = row->IsUserCollapsed;
|
||||
const bool visible = row->is_visible();
|
||||
std::map<int, Snowflake> child_rows;
|
||||
for (auto child : row->Children) {
|
||||
child_rows[child->get_index()] = child->ID;
|
||||
}
|
||||
guild_row->Children.erase(row);
|
||||
DeleteRow(row);
|
||||
|
||||
int pos = guild_row->get_index();
|
||||
const auto sorted = guild->GetSortedChannels(id);
|
||||
const auto sorted_it = std::find(sorted.begin(), sorted.end(), id);
|
||||
if (sorted_it == sorted.end()) return;
|
||||
if (std::next(sorted_it) == sorted.end()) {
|
||||
const auto x = m_id_to_row.find(*std::prev(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index() + 1;
|
||||
} else {
|
||||
const auto x = m_id_to_row.find(*std::next(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index();
|
||||
}
|
||||
|
||||
auto *new_row = Gtk::manage(new ChannelListRowCategory(&*data));
|
||||
new_row->IsUserCollapsed = old_collapsed;
|
||||
if (visible)
|
||||
new_row->show();
|
||||
m_id_to_row[id] = new_row;
|
||||
new_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), new_row->ID));
|
||||
new_row->Parent = guild_row;
|
||||
guild_row->Children.insert(new_row);
|
||||
m_list->insert(*new_row, pos);
|
||||
int i = 1;
|
||||
for (const auto &[idx, child_id] : child_rows) {
|
||||
const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(child_id);
|
||||
if (channel.has_value()) {
|
||||
auto *new_child = Gtk::manage(new ChannelListRowChannel(&*channel));
|
||||
new_row->Children.insert(new_child);
|
||||
new_child->Parent = new_row;
|
||||
new_child->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), new_child->ID));
|
||||
m_id_to_row[child_id] = new_child;
|
||||
if (visible && !new_row->IsUserCollapsed)
|
||||
new_child->show();
|
||||
m_list->insert(*new_child, pos + i++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// so is this
|
||||
void ChannelList::UpdateChannel(Snowflake id) {
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(*data->GuildID);
|
||||
const auto *guild_row = m_guild_id_to_row.at(*data->GuildID);
|
||||
if (data->Type == ChannelType::GUILD_CATEGORY) {
|
||||
UpdateChannelCategory(id);
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = m_id_to_row.find(id);
|
||||
if (it == m_id_to_row.end()) return; // stuff like voice doesnt have a row yet
|
||||
auto row = dynamic_cast<ChannelListRowChannel *>(it->second);
|
||||
const bool old_collapsed = row->IsUserCollapsed;
|
||||
const bool old_visible = row->is_visible();
|
||||
DeleteRow(row);
|
||||
|
||||
int pos = guild_row->get_index() + 1; // fallback
|
||||
const auto sorted = guild->GetSortedChannels();
|
||||
const auto sorted_it = std::find(sorted.begin(), sorted.end(), id);
|
||||
if (sorted_it + 1 == sorted.end()) {
|
||||
const auto x = m_id_to_row.find(*std::prev(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index() + 1;
|
||||
} else {
|
||||
const auto x = m_id_to_row.find(*std::next(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index();
|
||||
}
|
||||
|
||||
auto *new_row = Gtk::manage(new ChannelListRowChannel(&*data));
|
||||
new_row->IsUserCollapsed = old_collapsed;
|
||||
m_id_to_row[id] = new_row;
|
||||
if (data->ParentID.has_value()) {
|
||||
new_row->Parent = m_id_to_row.at(*data->ParentID);
|
||||
} else {
|
||||
new_row->Parent = m_guild_id_to_row.at(*data->GuildID);
|
||||
}
|
||||
new_row->Parent->Children.insert(new_row);
|
||||
if (new_row->Parent->is_visible() && !new_row->Parent->IsUserCollapsed)
|
||||
new_row->show();
|
||||
new_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), new_row->ID));
|
||||
m_list->insert(*new_row, pos);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateCreateDMChannel(Snowflake id) {
|
||||
const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(id);
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(&*chan));
|
||||
dm_row->IsUserCollapsed = false;
|
||||
m_list->insert(*dm_row, m_dm_header_row->get_index() + 1);
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
m_id_to_row[id] = dm_row;
|
||||
if (!m_dm_header_row->IsUserCollapsed)
|
||||
dm_row->show();
|
||||
}
|
||||
|
||||
void ChannelList::UpdateCreateChannel(Snowflake id) {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto data = discord.GetChannel(id);
|
||||
if (data->Type == ChannelType::DM || data->Type == ChannelType::GROUP_DM) {
|
||||
UpdateCreateDMChannel(id);
|
||||
return;
|
||||
}
|
||||
const auto guild = discord.GetGuild(*data->GuildID);
|
||||
auto *guild_row = m_guild_id_to_row.at(*data->GuildID);
|
||||
|
||||
int pos = guild_row->get_index() + 1;
|
||||
const auto sorted = guild->GetSortedChannels();
|
||||
const auto sorted_it = std::find(sorted.begin(), sorted.end(), id);
|
||||
if (sorted_it + 1 == sorted.end()) {
|
||||
const auto x = m_id_to_row.find(*std::prev(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index() + 1;
|
||||
} else {
|
||||
const auto x = m_id_to_row.find(*std::next(sorted_it));
|
||||
if (x != m_id_to_row.end())
|
||||
pos = x->second->get_index();
|
||||
}
|
||||
|
||||
ChannelListRow *row;
|
||||
if (data->Type == ChannelType::GUILD_TEXT || data->Type == ChannelType::GUILD_NEWS) {
|
||||
auto *tmp = Gtk::manage(new ChannelListRowChannel(&*data));
|
||||
tmp->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), tmp->ID));
|
||||
row = tmp;
|
||||
} else if (data->Type == ChannelType::GUILD_CATEGORY) {
|
||||
auto *tmp = Gtk::manage(new ChannelListRowCategory(&*data));
|
||||
tmp->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), tmp->ID));
|
||||
row = tmp;
|
||||
} else
|
||||
return;
|
||||
row->IsUserCollapsed = false;
|
||||
if (!guild_row->IsUserCollapsed)
|
||||
row->show();
|
||||
row->Parent = guild_row;
|
||||
guild_row->Children.insert(row);
|
||||
m_id_to_row[id] = row;
|
||||
m_list->insert(*row, pos);
|
||||
}
|
||||
|
||||
void ChannelList::UpdateGuild(Snowflake id) {
|
||||
// the only thing changed is the row containing the guild item so just recreate it
|
||||
const auto data = Abaddon::Get().GetDiscordClient().GetGuild(id);
|
||||
if (!data.has_value()) return;
|
||||
auto it = m_guild_id_to_row.find(id);
|
||||
if (it == m_guild_id_to_row.end()) return;
|
||||
auto *row = dynamic_cast<ChannelListRowGuild *>(it->second);
|
||||
const auto children = row->Children;
|
||||
const auto index = row->get_index();
|
||||
const bool old_collapsed = row->IsUserCollapsed;
|
||||
const bool old_gindex = row->GuildIndex;
|
||||
delete row;
|
||||
auto *new_row = Gtk::manage(new ChannelListRowGuild(&*data));
|
||||
new_row->IsUserCollapsed = old_collapsed;
|
||||
new_row->GuildIndex = old_gindex;
|
||||
m_guild_id_to_row[new_row->ID] = new_row;
|
||||
new_row->signal_leave().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuLeave), new_row->ID));
|
||||
new_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), new_row->ID));
|
||||
new_row->signal_settings().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuSettings), new_row->ID));
|
||||
new_row->Children = children;
|
||||
for (auto child : children)
|
||||
child->Parent = new_row;
|
||||
new_row->show_all();
|
||||
m_list->insert(*new_row, index);
|
||||
}
|
||||
|
||||
void ChannelList::SetActiveChannel(Snowflake id) {
|
||||
auto it = m_id_to_row.find(id);
|
||||
if (it == m_id_to_row.end()) return;
|
||||
m_list->select_row(*it->second);
|
||||
}
|
||||
|
||||
void ChannelList::CollapseRow(ChannelListRow *row) {
|
||||
row->Collapse();
|
||||
for (auto child : row->Children) {
|
||||
child->hide();
|
||||
CollapseRow(child);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::ExpandRow(ChannelListRow *row) {
|
||||
row->Expand();
|
||||
row->show();
|
||||
if (!row->IsUserCollapsed)
|
||||
for (auto child : row->Children)
|
||||
ExpandRow(child);
|
||||
}
|
||||
|
||||
void ChannelList::DeleteRow(ChannelListRow *row) {
|
||||
for (auto child : row->Children)
|
||||
DeleteRow(child);
|
||||
if (row->Parent != nullptr)
|
||||
row->Parent->Children.erase(row);
|
||||
else
|
||||
printf("row has no parent!\n");
|
||||
if (dynamic_cast<ChannelListRowGuild *>(row) != nullptr)
|
||||
m_guild_id_to_row.erase(row->ID);
|
||||
else
|
||||
m_id_to_row.erase(row->ID);
|
||||
delete row;
|
||||
}
|
||||
|
||||
void ChannelList::on_row_activated(Gtk::ListBoxRow *tmprow) {
|
||||
auto row = dynamic_cast<ChannelListRow *>(tmprow);
|
||||
if (row == nullptr) return;
|
||||
bool new_collapsed = !row->IsUserCollapsed;
|
||||
row->IsUserCollapsed = new_collapsed;
|
||||
|
||||
// kinda ugly
|
||||
if (dynamic_cast<ChannelListRowChannel *>(row) != nullptr || dynamic_cast<ChannelListRowDMChannel *>(row) != nullptr)
|
||||
m_signal_action_channel_item_select.emit(row->ID);
|
||||
|
||||
if (new_collapsed)
|
||||
CollapseRow(row);
|
||||
else
|
||||
ExpandRow(row);
|
||||
}
|
||||
|
||||
void ChannelList::InsertGuildAt(Snowflake id, int pos) {
|
||||
const auto insert_and_adjust = [&](Gtk::Widget &widget) {
|
||||
m_list->insert(widget, pos);
|
||||
if (pos != -1) pos++;
|
||||
};
|
||||
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto guild_data = discord.GetGuild(id);
|
||||
if (!guild_data.has_value()) return;
|
||||
|
||||
std::map<int, ChannelData> orphan_channels;
|
||||
std::unordered_map<Snowflake, std::vector<ChannelData>> cat_to_channels;
|
||||
if (guild_data->Channels.has_value())
|
||||
for (const auto &dc : *guild_data->Channels) {
|
||||
const auto channel = discord.GetChannel(dc.ID);
|
||||
if (!channel.has_value()) continue;
|
||||
if (channel->Type != ChannelType::GUILD_TEXT && channel->Type != ChannelType::GUILD_NEWS) continue;
|
||||
|
||||
if (channel->ParentID.has_value())
|
||||
cat_to_channels[*channel->ParentID].push_back(*channel);
|
||||
else
|
||||
orphan_channels[*channel->Position] = *channel;
|
||||
}
|
||||
|
||||
auto *guild_row = Gtk::manage(new ChannelListRowGuild(&*guild_data));
|
||||
guild_row->show_all();
|
||||
guild_row->IsUserCollapsed = true;
|
||||
guild_row->GuildIndex = m_guild_count++;
|
||||
insert_and_adjust(*guild_row);
|
||||
m_guild_id_to_row[guild_row->ID] = guild_row;
|
||||
guild_row->signal_leave().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuLeave), guild_row->ID));
|
||||
guild_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), guild_row->ID));
|
||||
guild_row->signal_settings().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnGuildMenuSettings), guild_row->ID));
|
||||
|
||||
// add channels with no parent category
|
||||
for (const auto &[pos, channel] : orphan_channels) {
|
||||
auto *chan_row = Gtk::manage(new ChannelListRowChannel(&channel));
|
||||
chan_row->IsUserCollapsed = false;
|
||||
chan_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), chan_row->ID));
|
||||
insert_and_adjust(*chan_row);
|
||||
guild_row->Children.insert(chan_row);
|
||||
chan_row->Parent = guild_row;
|
||||
m_id_to_row[chan_row->ID] = chan_row;
|
||||
}
|
||||
|
||||
// categories
|
||||
std::map<int, std::vector<ChannelData>> sorted_categories;
|
||||
if (guild_data->Channels.has_value())
|
||||
for (const auto &dc : *guild_data->Channels) {
|
||||
const auto channel = discord.GetChannel(dc.ID);
|
||||
if (!channel.has_value()) continue;
|
||||
if (channel->Type == ChannelType::GUILD_CATEGORY)
|
||||
sorted_categories[*channel->Position].push_back(*channel);
|
||||
}
|
||||
|
||||
for (auto &[pos, catvec] : sorted_categories) {
|
||||
std::sort(catvec.begin(), catvec.end(), [](const ChannelData &a, const ChannelData &b) { return a.ID < b.ID; });
|
||||
for (const auto cat : catvec) {
|
||||
auto *cat_row = Gtk::manage(new ChannelListRowCategory(&cat));
|
||||
cat_row->IsUserCollapsed = false;
|
||||
cat_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), cat_row->ID));
|
||||
insert_and_adjust(*cat_row);
|
||||
guild_row->Children.insert(cat_row);
|
||||
cat_row->Parent = guild_row;
|
||||
m_id_to_row[cat_row->ID] = cat_row;
|
||||
|
||||
// child channels
|
||||
if (cat_to_channels.find(cat.ID) == cat_to_channels.end()) continue;
|
||||
std::map<int, ChannelData> sorted_channels;
|
||||
|
||||
for (const auto channel : cat_to_channels.at(cat.ID))
|
||||
sorted_channels[*channel.Position] = channel;
|
||||
|
||||
for (const auto &[pos, channel] : sorted_channels) {
|
||||
auto *chan_row = Gtk::manage(new ChannelListRowChannel(&channel));
|
||||
chan_row->IsUserCollapsed = false;
|
||||
chan_row->signal_copy_id().connect(sigc::bind(sigc::mem_fun(*this, &ChannelList::OnMenuCopyID), chan_row->ID));
|
||||
insert_and_adjust(*chan_row);
|
||||
cat_row->Children.insert(chan_row);
|
||||
chan_row->Parent = cat_row;
|
||||
m_id_to_row[chan_row->ID] = chan_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::AddPrivateChannels() {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto dms_ = discord.GetPrivateChannels();
|
||||
std::vector<ChannelData> dms;
|
||||
for (const auto &x : dms_) {
|
||||
const auto chan = discord.GetChannel(x);
|
||||
dms.push_back(*chan);
|
||||
}
|
||||
std::sort(dms.begin(), dms.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
|
||||
return a.LastMessageID > b.LastMessageID;
|
||||
});
|
||||
|
||||
m_dm_header_row = Gtk::manage(new ChannelListRowDMHeader);
|
||||
m_dm_header_row->show_all();
|
||||
m_dm_header_row->IsUserCollapsed = true;
|
||||
m_list->add(*m_dm_header_row);
|
||||
|
||||
for (const auto &dm : dms) {
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(&dm));
|
||||
dm_row->Parent = m_dm_header_row;
|
||||
m_id_to_row[dm.ID] = dm_row;
|
||||
dm_row->IsUserCollapsed = false;
|
||||
m_list->add(*dm_row);
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::UpdateListing() {
|
||||
std::unordered_set<Snowflake> guilds = Abaddon::Get().GetDiscordClient().GetGuilds();
|
||||
|
||||
auto children = m_list->get_children();
|
||||
auto it = children.begin();
|
||||
|
||||
while (it != children.end()) {
|
||||
delete *it;
|
||||
it++;
|
||||
}
|
||||
|
||||
m_id_to_row.clear();
|
||||
|
||||
m_guild_count = 0;
|
||||
|
||||
AddPrivateChannels();
|
||||
|
||||
auto sorted_guilds = Abaddon::Get().GetDiscordClient().GetUserSortedGuilds();
|
||||
for (auto gid : sorted_guilds) {
|
||||
InsertGuildAt(gid, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelList::OnMenuCopyID(Snowflake id) {
|
||||
Gtk::Clipboard::get()->set_text(std::to_string(id));
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildMenuLeave(Snowflake id) {
|
||||
m_signal_action_guild_leave.emit(id);
|
||||
}
|
||||
|
||||
void ChannelList::OnGuildMenuSettings(Snowflake id) {
|
||||
m_signal_action_guild_settings.emit(id);
|
||||
}
|
||||
|
||||
void ChannelList::CheckBumpDM(Snowflake channel_id) {
|
||||
auto it = m_id_to_row.find(channel_id);
|
||||
if (it == m_id_to_row.end()) return;
|
||||
auto *row = it->second;
|
||||
const auto index = row->get_index();
|
||||
if (index == 1) return; // 1 is top of dm list
|
||||
const bool selected = row->is_selected();
|
||||
row->Parent->Children.erase(row);
|
||||
delete row;
|
||||
const auto chan = Abaddon::Get().GetDiscordClient().GetChannel(channel_id);
|
||||
auto *dm_row = Gtk::manage(new ChannelListRowDMChannel(&*chan));
|
||||
dm_row->Parent = m_dm_header_row;
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
m_id_to_row[channel_id] = dm_row;
|
||||
dm_row->IsUserCollapsed = false;
|
||||
m_list->insert(*dm_row, 1);
|
||||
m_dm_header_row->Children.insert(dm_row);
|
||||
if (selected)
|
||||
m_list->select_row(*dm_row);
|
||||
if (m_dm_header_row->is_visible() && !m_dm_header_row->IsUserCollapsed)
|
||||
dm_row->show();
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_channel_item_select ChannelList::signal_action_channel_item_select() {
|
||||
return m_signal_action_channel_item_select;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_leave ChannelList::signal_action_guild_leave() {
|
||||
return m_signal_action_guild_leave;
|
||||
}
|
||||
|
||||
ChannelList::type_signal_action_guild_settings ChannelList::signal_action_guild_settings() {
|
||||
return m_signal_action_guild_settings;
|
||||
}
|
||||
191
components/channels.hpp
Normal file
191
components/channels.hpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include "../discord/discord.hpp"
|
||||
|
||||
static const constexpr int ChannelEmojiSize = 16;
|
||||
|
||||
class ChannelListRow : public Gtk::ListBoxRow {
|
||||
public:
|
||||
bool IsUserCollapsed;
|
||||
Snowflake ID;
|
||||
std::unordered_set<ChannelListRow *> Children;
|
||||
ChannelListRow *Parent = nullptr;
|
||||
|
||||
virtual void Collapse();
|
||||
virtual void Expand();
|
||||
|
||||
static void MakeReadOnly(Gtk::TextView *tv);
|
||||
};
|
||||
|
||||
class ChannelListRowDMHeader : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowDMHeader();
|
||||
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
Gtk::Label *m_lbl;
|
||||
};
|
||||
|
||||
class StatusIndicator;
|
||||
class ChannelListRowDMChannel : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowDMChannel(const ChannelData *data);
|
||||
|
||||
protected:
|
||||
Gtk::EventBox *m_ev;
|
||||
Gtk::Box *m_box;
|
||||
StatusIndicator *m_status = nullptr;
|
||||
Gtk::Widget *m_lbl;
|
||||
Gtk::Image *m_icon = nullptr;
|
||||
|
||||
Gtk::Menu m_menu;
|
||||
Gtk::MenuItem *m_menu_close; // leave if group
|
||||
Gtk::MenuItem *m_menu_copy_id;
|
||||
};
|
||||
|
||||
class ChannelListRowGuild : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowGuild(const GuildData *data);
|
||||
|
||||
int GuildIndex;
|
||||
|
||||
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:
|
||||
typedef sigc::signal<void> type_signal_copy_id;
|
||||
typedef sigc::signal<void> type_signal_leave;
|
||||
typedef sigc::signal<void> type_signal_settings;
|
||||
|
||||
type_signal_copy_id m_signal_copy_id;
|
||||
type_signal_leave m_signal_leave;
|
||||
type_signal_settings m_signal_settings;
|
||||
|
||||
public:
|
||||
type_signal_copy_id signal_copy_id();
|
||||
type_signal_leave signal_leave();
|
||||
type_signal_settings signal_settings();
|
||||
};
|
||||
|
||||
class ChannelListRowCategory : public ChannelListRow {
|
||||
public:
|
||||
ChannelListRowCategory(const ChannelData *data);
|
||||
|
||||
virtual void Collapse();
|
||||
virtual void Expand();
|
||||
|
||||
protected:
|
||||
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 UpdateCreateDMChannel(Snowflake id);
|
||||
void UpdateCreateChannel(Snowflake id);
|
||||
void UpdateGuild(Snowflake id);
|
||||
|
||||
void SetActiveChannel(Snowflake id);
|
||||
|
||||
protected:
|
||||
Gtk::ListBox *m_list;
|
||||
Gtk::ScrolledWindow *m_main;
|
||||
|
||||
ChannelListRowDMHeader *m_dm_header_row = nullptr;
|
||||
|
||||
void CollapseRow(ChannelListRow *row);
|
||||
void ExpandRow(ChannelListRow *row);
|
||||
void DeleteRow(ChannelListRow *row);
|
||||
|
||||
void UpdateChannelCategory(Snowflake id);
|
||||
|
||||
void on_row_activated(Gtk::ListBoxRow *row);
|
||||
|
||||
int m_guild_count;
|
||||
void OnMenuCopyID(Snowflake id);
|
||||
void OnGuildMenuLeave(Snowflake id);
|
||||
void OnGuildMenuSettings(Snowflake id);
|
||||
|
||||
Gtk::Menu m_channel_menu;
|
||||
Gtk::MenuItem *m_channel_menu_copyid;
|
||||
|
||||
// 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 InsertGuildAt(Snowflake id, int pos);
|
||||
|
||||
void AddPrivateChannels();
|
||||
|
||||
void CheckBumpDM(Snowflake channel_id);
|
||||
|
||||
public:
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_channel_item_select;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_guild_leave;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_action_guild_settings;
|
||||
|
||||
type_signal_action_channel_item_select signal_action_channel_item_select();
|
||||
type_signal_action_guild_leave signal_action_guild_leave();
|
||||
type_signal_action_guild_settings signal_action_guild_settings();
|
||||
|
||||
protected:
|
||||
type_signal_action_channel_item_select m_signal_action_channel_item_select;
|
||||
type_signal_action_guild_leave m_signal_action_guild_leave;
|
||||
type_signal_action_guild_settings m_signal_action_guild_settings;
|
||||
};
|
||||
66
components/chatinput.cpp
Normal file
66
components/chatinput.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "chatinput.hpp"
|
||||
|
||||
ChatInput::ChatInput() {
|
||||
get_style_context()->add_class("message-input");
|
||||
set_propagate_natural_height(true);
|
||||
set_min_content_height(20);
|
||||
set_max_content_height(250);
|
||||
set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
|
||||
// hack
|
||||
auto cb = [this](GdkEventKey *e) -> bool {
|
||||
return event(reinterpret_cast<GdkEvent *>(e));
|
||||
};
|
||||
m_textview.signal_key_press_event().connect(cb, false);
|
||||
m_textview.set_hexpand(false);
|
||||
m_textview.set_halign(Gtk::ALIGN_FILL);
|
||||
m_textview.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_textview.set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||
m_textview.show();
|
||||
add(m_textview);
|
||||
}
|
||||
|
||||
void ChatInput::InsertText(const Glib::ustring &text) {
|
||||
GetBuffer()->insert_at_cursor(text);
|
||||
m_textview.grab_focus();
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
|
||||
return m_textview.get_buffer();
|
||||
}
|
||||
|
||||
// this isnt connected directly so that the chat window can handle stuff like the completer first
|
||||
bool ChatInput::ProcessKeyPress(GdkEventKey *event) {
|
||||
if (event->keyval == GDK_KEY_Escape) {
|
||||
m_signal_escape.emit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->keyval == GDK_KEY_Return) {
|
||||
if (event->state & GDK_SHIFT_MASK)
|
||||
return false;
|
||||
|
||||
auto buf = GetBuffer();
|
||||
auto text = buf->get_text();
|
||||
|
||||
const bool accepted = m_signal_submit.emit(text);
|
||||
if (accepted)
|
||||
buf->set_text("");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatInput::on_grab_focus() {
|
||||
m_textview.grab_focus();
|
||||
}
|
||||
|
||||
ChatInput::type_signal_submit ChatInput::signal_submit() {
|
||||
return m_signal_submit;
|
||||
}
|
||||
|
||||
ChatInput::type_signal_escape ChatInput::signal_escape() {
|
||||
return m_signal_escape;
|
||||
}
|
||||
28
components/chatinput.hpp
Normal file
28
components/chatinput.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
|
||||
class ChatInput : public Gtk::ScrolledWindow {
|
||||
public:
|
||||
ChatInput();
|
||||
|
||||
void InsertText(const Glib::ustring &text);
|
||||
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
|
||||
bool ProcessKeyPress(GdkEventKey *event);
|
||||
|
||||
protected:
|
||||
void on_grab_focus() override;
|
||||
|
||||
private:
|
||||
Gtk::TextView m_textview;
|
||||
|
||||
public:
|
||||
typedef sigc::signal<bool, Glib::ustring> type_signal_submit;
|
||||
typedef sigc::signal<void> type_signal_escape;
|
||||
|
||||
type_signal_submit signal_submit();
|
||||
type_signal_escape signal_escape();
|
||||
|
||||
private:
|
||||
type_signal_submit m_signal_submit;
|
||||
type_signal_escape m_signal_escape;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#include <filesystem>
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
|
||||
constexpr static const int MaxUsersInIndicator = 4;
|
||||
|
||||
@@ -21,20 +21,19 @@ 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;
|
||||
GetImageDimensions(inw, inh, w, h, 20, 10);
|
||||
loader->set_size(w, h);
|
||||
});
|
||||
loader->write(gif_data.data(), gif_data.size());
|
||||
try {
|
||||
loader->signal_size_prepared().connect([&](int inw, int inh) {
|
||||
int w, h;
|
||||
GetImageDimensions(inw, inh, w, h, 20, 10);
|
||||
loader->set_size(w, h);
|
||||
});
|
||||
loader->write(gif_data.data(), gif_data.size());
|
||||
loader->close();
|
||||
m_img.property_pixbuf_animation() = loader->get_animation();
|
||||
} catch (...) {}
|
||||
} catch (const std::exception &) {}
|
||||
}
|
||||
|
||||
void ChatInputIndicator::AddUser(Snowflake channel_id, const UserData &user, int timeout) {
|
||||
@@ -84,14 +83,14 @@ void ChatInputIndicator::OnMessageCreate(const Message &message) {
|
||||
|
||||
void ChatInputIndicator::SetTypingString(const Glib::ustring &str) {
|
||||
m_label.set_text(str);
|
||||
if (str.empty())
|
||||
if (str == "")
|
||||
m_img.hide();
|
||||
else if (m_img.property_pixbuf_animation().get_value())
|
||||
m_img.show();
|
||||
}
|
||||
|
||||
void ChatInputIndicator::ComputeTypingString() {
|
||||
if (!m_custom_markup.empty()) {
|
||||
if (m_custom_markup != "") {
|
||||
m_label.set_markup(m_custom_markup);
|
||||
m_img.hide();
|
||||
return;
|
||||
@@ -104,7 +103,7 @@ void ChatInputIndicator::ComputeTypingString() {
|
||||
if (user.has_value())
|
||||
typers.push_back(*user);
|
||||
}
|
||||
if (typers.empty()) {
|
||||
if (typers.size() == 0) {
|
||||
SetTypingString("");
|
||||
} else if (typers.size() == 1) {
|
||||
SetTypingString(typers[0].Username + " is typing...");
|
||||
@@ -112,7 +111,7 @@ void ChatInputIndicator::ComputeTypingString() {
|
||||
SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing...");
|
||||
} else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) {
|
||||
Glib::ustring str;
|
||||
for (size_t i = 0; i < typers.size() - 1; i++)
|
||||
for (int i = 0; i < typers.size() - 1; i++)
|
||||
str += typers[i].Username + ", ";
|
||||
SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing...");
|
||||
} else { // size() > MaxUsersInIndicator
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include "discord/message.hpp"
|
||||
#include "discord/user.hpp"
|
||||
#include "../discord/message.hpp"
|
||||
#include "../discord/user.hpp"
|
||||
|
||||
class ChatInputIndicator : public Gtk::Box {
|
||||
public:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/discord.hpp"
|
||||
#include "../discord/discord.hpp"
|
||||
|
||||
class ChatMessageItemContainer : public Gtk::EventBox {
|
||||
class ChatMessageItemContainer : public Gtk::Box {
|
||||
public:
|
||||
Snowflake ID;
|
||||
Snowflake ChannelID;
|
||||
@@ -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();
|
||||
@@ -19,14 +19,13 @@ public:
|
||||
void SetFailed();
|
||||
|
||||
protected:
|
||||
static void AddClickHandler(Gtk::Widget *widget, const std::string &);
|
||||
Gtk::TextView *CreateTextComponent(const Message &data); // Message.Content
|
||||
void AddClickHandler(Gtk::Widget *widget, std::string);
|
||||
Gtk::TextView *CreateTextComponent(const Message *data); // Message.Content
|
||||
void UpdateTextComponent(Gtk::TextView *tv);
|
||||
Gtk::Widget *CreateEmbedsComponent(const std::vector<EmbedData> &embeds);
|
||||
static Gtk::Widget *CreateEmbedComponent(const EmbedData &data); // Message.Embeds[0]
|
||||
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 *CreateStickersComponent(const std::vector<StickerItem> &data);
|
||||
Gtk::Widget *CreateStickerComponent(const StickerData &data);
|
||||
Gtk::Widget *CreateReactionsComponent(const Message &data);
|
||||
Gtk::Widget *CreateReplyComponent(const Message &data);
|
||||
|
||||
@@ -34,17 +33,15 @@ protected:
|
||||
|
||||
static bool IsEmbedImageOnly(const EmbedData &data);
|
||||
|
||||
static void HandleRoleMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf);
|
||||
void HandleUserMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf) const;
|
||||
static void HandleStockEmojis(Gtk::TextView &tv);
|
||||
static void HandleCustomEmojis(Gtk::TextView &tv);
|
||||
static void HandleEmojis(Gtk::TextView &tv);
|
||||
static void CleanupEmojis(const Glib::RefPtr<Gtk::TextBuffer> &buf);
|
||||
void HandleUserMentions(Glib::RefPtr<Gtk::TextBuffer> buf);
|
||||
void HandleStockEmojis(Gtk::TextView &tv);
|
||||
void HandleCustomEmojis(Gtk::TextView &tv);
|
||||
void HandleEmojis(Gtk::TextView &tv);
|
||||
void CleanupEmojis(Glib::RefPtr<Gtk::TextBuffer> buf);
|
||||
|
||||
void HandleChannelMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf);
|
||||
void HandleChannelMentions(Glib::RefPtr<Gtk::TextBuffer> buf);
|
||||
void HandleChannelMentions(Gtk::TextView *tv);
|
||||
bool OnClickChannel(GdkEventButton *ev);
|
||||
bool OnTextViewButtonPress(GdkEventButton *ev);
|
||||
|
||||
// reused for images and links
|
||||
Gtk::Menu m_link_menu;
|
||||
@@ -58,8 +55,24 @@ protected:
|
||||
std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_link_tagmap;
|
||||
std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap;
|
||||
|
||||
Gtk::EventBox *_ev;
|
||||
Gtk::Box m_main;
|
||||
void AttachEventHandlers(Gtk::Widget &widget);
|
||||
void ShowMenu(GdkEvent *event);
|
||||
|
||||
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,29 +111,28 @@ public:
|
||||
Snowflake ChannelID;
|
||||
Snowflake NewestID = 0;
|
||||
|
||||
ChatMessageHeader(const Message &data);
|
||||
ChatMessageHeader(const Message *data);
|
||||
void AddContent(Gtk::Widget *widget, bool prepend);
|
||||
void UpdateName();
|
||||
std::vector<Gtk::Widget *> GetChildContent();
|
||||
void UpdateNameColor();
|
||||
std::vector<Gtk::Widget*> GetChildContent();
|
||||
|
||||
protected:
|
||||
void AttachUserMenuHandler(Gtk::Widget &widget);
|
||||
static Glib::ustring GetEscapedDisplayName(const UserData &user, const std::optional<GuildMember> &member);
|
||||
|
||||
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;
|
||||
411
components/chatwindow.cpp
Normal file
411
components/chatwindow.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
#include "chatwindow.hpp"
|
||||
#include "chatmessage.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "chatinputindicator.hpp"
|
||||
#include "ratelimitindicator.hpp"
|
||||
#include "chatinput.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_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);
|
||||
m_meta = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
|
||||
m_rate_limit_indicator->set_margin_end(5);
|
||||
m_rate_limit_indicator->set_hexpand(true);
|
||||
m_rate_limit_indicator->set_halign(Gtk::ALIGN_END);
|
||||
m_rate_limit_indicator->set_valign(Gtk::ALIGN_END);
|
||||
m_rate_limit_indicator->show();
|
||||
|
||||
m_input_indicator->set_halign(Gtk::ALIGN_START);
|
||||
m_input_indicator->set_valign(Gtk::ALIGN_END);
|
||||
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_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]() {
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
});
|
||||
m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::OnKeyPressEvent), false);
|
||||
m_input->show();
|
||||
|
||||
m_completer.SetBuffer(m_input->GetBuffer());
|
||||
m_completer.SetGetChannelID([this]() -> auto {
|
||||
return m_active_channel;
|
||||
});
|
||||
|
||||
m_completer.SetGetRecentAuthors([this]() -> auto {
|
||||
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_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_scroll);
|
||||
m_main->add(m_completer);
|
||||
m_main->add(*m_input);
|
||||
m_main->add(*m_meta);
|
||||
m_main->show();
|
||||
}
|
||||
|
||||
Gtk::Widget *ChatWindow::GetRoot() const {
|
||||
return m_main;
|
||||
}
|
||||
|
||||
void ChatWindow::Clear() {
|
||||
SetMessages(std::set<Snowflake>());
|
||||
}
|
||||
|
||||
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_input_indicator->SetActiveChannel(id);
|
||||
m_rate_limit_indicator->SetActiveChannel(id);
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
}
|
||||
|
||||
void ChatWindow::AddNewMessage(Snowflake id) {
|
||||
ProcessNewMessage(id, false);
|
||||
}
|
||||
|
||||
void ChatWindow::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 ChatWindow::UpdateMessage(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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
m_input->InsertText(text);
|
||||
}
|
||||
|
||||
Snowflake ChatWindow::GetOldestListedMessage() {
|
||||
return m_id_to_widget.begin()->first;
|
||||
}
|
||||
|
||||
void ChatWindow::UpdateReactions(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();
|
||||
}
|
||||
|
||||
Snowflake ChatWindow::GetActiveChannel() const {
|
||||
return m_active_channel;
|
||||
}
|
||||
|
||||
bool ChatWindow::OnInputSubmit(const Glib::ustring &text) {
|
||||
if (!m_rate_limit_indicator->CanSpeak())
|
||||
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)
|
||||
StopReplying();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {
|
||||
if (m_completer.ProcessKeyPress(e))
|
||||
return true;
|
||||
|
||||
if (m_input->ProcessKeyPress(e))
|
||||
return true;
|
||||
|
||||
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);
|
||||
const auto author = discord.GetUser(message.Author.ID);
|
||||
m_replying_to = message_id;
|
||||
m_is_replying = true;
|
||||
m_input->grab_focus();
|
||||
m_input->get_style_context()->add_class("replying");
|
||||
if (author.has_value())
|
||||
m_input_indicator->SetCustomMarkup("Replying to " + author->GetEscapedBoldString<false>());
|
||||
else
|
||||
m_input_indicator->SetCustomMarkup("Replying...");
|
||||
}
|
||||
|
||||
void ChatWindow::StopReplying() {
|
||||
m_is_replying = false;
|
||||
m_replying_to = Snowflake::Invalid;
|
||||
m_input->get_style_context()->remove_class("replying");
|
||||
m_input_indicator->ClearCustom();
|
||||
}
|
||||
|
||||
void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) {
|
||||
if (pos == Gtk::POS_TOP)
|
||||
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) {
|
||||
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() {
|
||||
return m_signal_action_message_edit;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_chat_submit ChatWindow::signal_action_chat_submit() {
|
||||
return m_signal_action_chat_submit;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_chat_load_history ChatWindow::signal_action_chat_load_history() {
|
||||
return m_signal_action_chat_load_history;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_channel_click ChatWindow::signal_action_channel_click() {
|
||||
return m_signal_action_channel_click;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_insert_mention ChatWindow::signal_action_insert_mention() {
|
||||
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;
|
||||
}
|
||||
|
||||
ChatWindow::type_signal_action_reaction_remove ChatWindow::signal_action_reaction_remove() {
|
||||
return m_signal_action_reaction_remove;
|
||||
}
|
||||
@@ -2,22 +2,14 @@
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "discord/discord.hpp"
|
||||
#include "discord/chatsubmitparams.hpp"
|
||||
#include "../discord/discord.hpp"
|
||||
#include "completer.hpp"
|
||||
#include "state.hpp"
|
||||
#include "progressbar.hpp"
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
class ChannelTabSwitcherHandy;
|
||||
#endif
|
||||
|
||||
class ChatMessageHeader;
|
||||
class ChatMessageItemContainer;
|
||||
class ChatInput;
|
||||
class ChatInputIndicator;
|
||||
class RateLimitIndicator;
|
||||
class ChatList;
|
||||
class ChatWindow {
|
||||
public:
|
||||
ChatWindow();
|
||||
@@ -26,53 +18,47 @@ 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 InsertChatInput(const std::string &text);
|
||||
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);
|
||||
void AddAttachment(const Glib::RefPtr<Gio::File> &file);
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void OpenNewTab(Snowflake id);
|
||||
TabsState GetTabsState();
|
||||
void UseTabsState(const TabsState &state);
|
||||
void GoBack();
|
||||
void GoForward();
|
||||
void GoToPreviousTab();
|
||||
void GoToNextTab();
|
||||
void GoToTab(int idx);
|
||||
#endif
|
||||
|
||||
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(ChatSubmitParams data);
|
||||
bool OnInputSubmit(const Glib::ustring &text);
|
||||
|
||||
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;
|
||||
|
||||
@@ -80,35 +66,36 @@ protected:
|
||||
ChatInputIndicator *m_input_indicator;
|
||||
RateLimitIndicator *m_rate_limit_indicator;
|
||||
Gtk::Box *m_meta;
|
||||
MessageUploadProgressBar m_progress;
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
ChannelTabSwitcherHandy *m_tab_switcher;
|
||||
#endif
|
||||
|
||||
public:
|
||||
using type_signal_action_message_edit = sigc::signal<void, Snowflake, Snowflake>;
|
||||
using type_signal_action_chat_submit = sigc::signal<void, ChatSubmitParams>;
|
||||
using type_signal_action_chat_load_history = sigc::signal<void, Snowflake>;
|
||||
using type_signal_action_channel_click = sigc::signal<void, Snowflake, bool>;
|
||||
using type_signal_action_insert_mention = sigc::signal<void, 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>;
|
||||
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;
|
||||
};
|
||||
@@ -1,8 +1,7 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include "completer.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
|
||||
constexpr const int CompleterHeight = 150;
|
||||
constexpr const int MaxCompleterEntries = 30;
|
||||
@@ -47,23 +46,23 @@ bool Completer::ProcessKeyPress(GdkEventKey *e) {
|
||||
|
||||
switch (e->keyval) {
|
||||
case GDK_KEY_Down: {
|
||||
if (m_entries.empty()) return true;
|
||||
const auto index = static_cast<size_t>(m_list.get_selected_row()->get_index());
|
||||
if (m_entries.size() == 0) return true;
|
||||
const int index = m_list.get_selected_row()->get_index();
|
||||
if (index >= m_entries.size() - 1) return true;
|
||||
m_list.select_row(*m_entries[index + 1]);
|
||||
ScrollListBoxToSelected(m_list);
|
||||
}
|
||||
return true;
|
||||
case GDK_KEY_Up: {
|
||||
if (m_entries.empty()) return true;
|
||||
const auto index = static_cast<size_t>(m_list.get_selected_row()->get_index());
|
||||
if (m_entries.size() == 0) return true;
|
||||
const int index = m_list.get_selected_row()->get_index();
|
||||
if (index == 0) return true;
|
||||
m_list.select_row(*m_entries[index - 1]);
|
||||
ScrollListBoxToSelected(m_list);
|
||||
}
|
||||
return true;
|
||||
case GDK_KEY_Return: {
|
||||
if (m_entries.empty()) return true;
|
||||
if (m_entries.size() == 0) return true;
|
||||
DoCompletion(m_list.get_selected_row());
|
||||
}
|
||||
return true;
|
||||
@@ -75,11 +74,11 @@ bool Completer::ProcessKeyPress(GdkEventKey *e) {
|
||||
}
|
||||
|
||||
void Completer::SetGetRecentAuthors(get_recent_authors_cb cb) {
|
||||
m_recent_authors_cb = std::move(cb);
|
||||
m_recent_authors_cb = cb;
|
||||
}
|
||||
|
||||
void Completer::SetGetChannelID(get_channel_id_cb cb) {
|
||||
m_channel_id_cb = std::move(cb);
|
||||
m_channel_id_cb = cb;
|
||||
}
|
||||
|
||||
bool Completer::IsShown() const {
|
||||
@@ -87,7 +86,7 @@ bool Completer::IsShown() const {
|
||||
}
|
||||
|
||||
CompleterEntry *Completer::CreateEntry(const Glib::ustring &completion) {
|
||||
auto entry = Gtk::manage(new CompleterEntry(completion, static_cast<int>(m_entries.size())));
|
||||
auto entry = Gtk::manage(new CompleterEntry(completion, m_entries.size()));
|
||||
m_entries.push_back(entry);
|
||||
entry->show_all();
|
||||
m_list.add(*entry);
|
||||
@@ -153,7 +152,7 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
|
||||
const auto make_entry = [&](const Glib::ustring &name, const Glib::ustring &completion, const Glib::ustring &url = "", bool animated = false) -> CompleterEntry * {
|
||||
const auto entry = CreateEntry(completion);
|
||||
entry->SetText(name);
|
||||
if (url.empty()) return entry;
|
||||
if (url == "") return entry;
|
||||
if (animated)
|
||||
entry->SetAnimation(url);
|
||||
else
|
||||
@@ -170,12 +169,12 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
|
||||
const auto guild = discord.GetGuild(*channel->GuildID);
|
||||
|
||||
if (guild.has_value() && guild->Emojis.has_value())
|
||||
for (const auto &tmp : *guild->Emojis) {
|
||||
for (const auto tmp : *guild->Emojis) {
|
||||
const auto emoji = *discord.GetEmoji(tmp.ID);
|
||||
if (emoji.IsAnimated.has_value() && *emoji.IsAnimated) continue;
|
||||
if (emoji.IsAvailable.has_value() && !*emoji.IsAvailable) continue;
|
||||
if (emoji.Roles.has_value() && !emoji.Roles->empty()) continue;
|
||||
if (!term.empty())
|
||||
if (emoji.Roles.has_value() && emoji.Roles->size() > 0) continue;
|
||||
if (term.size() > 0)
|
||||
if (!StringContainsCaseless(emoji.Name, term)) continue;
|
||||
|
||||
if (i++ > MaxCompleterEntries) break;
|
||||
@@ -187,12 +186,12 @@ void Completer::CompleteEmojis(const Glib::ustring &term) {
|
||||
for (const auto guild_id : discord.GetGuilds()) {
|
||||
const auto guild = discord.GetGuild(guild_id);
|
||||
if (!guild.has_value()) continue;
|
||||
for (const auto &tmp : *guild->Emojis) {
|
||||
for (const auto tmp : *guild->Emojis) {
|
||||
const auto emoji = *discord.GetEmoji(tmp.ID);
|
||||
const bool is_animated = emoji.IsAnimated.has_value() && *emoji.IsAnimated;
|
||||
if (emoji.IsAvailable.has_value() && !*emoji.IsAvailable) continue;
|
||||
if (emoji.Roles.has_value() && !emoji.Roles->empty()) continue;
|
||||
if (!term.empty())
|
||||
if (emoji.Roles.has_value() && emoji.Roles->size() > 0) continue;
|
||||
if (term.size() > 0)
|
||||
if (!StringContainsCaseless(emoji.Name, term)) continue;
|
||||
|
||||
if (i++ > MaxCompleterEntries) goto done;
|
||||
@@ -276,7 +275,7 @@ void Completer::OnTextBufferChanged() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!m_entries.empty()) {
|
||||
if (m_entries.size() > 0) {
|
||||
m_list.select_row(*m_entries[0]);
|
||||
set_reveal_child(true);
|
||||
} else {
|
||||
@@ -292,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;
|
||||
@@ -309,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;
|
||||
@@ -330,9 +329,9 @@ Glib::ustring Completer::GetTerm() {
|
||||
return m_start.get_text(m_end);
|
||||
}
|
||||
|
||||
CompleterEntry::CompleterEntry(Glib::ustring completion, int index)
|
||||
: m_completion(std::move(completion))
|
||||
, m_index(index)
|
||||
CompleterEntry::CompleterEntry(const Glib::ustring &completion, int index)
|
||||
: m_index(index)
|
||||
, m_completion(completion)
|
||||
, m_box(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
set_halign(Gtk::ALIGN_START);
|
||||
get_style_context()->add_class("completer-entry");
|
||||
@@ -2,13 +2,13 @@
|
||||
#include <gtkmm.h>
|
||||
#include <functional>
|
||||
#include "lazyimage.hpp"
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "../discord/snowflake.hpp"
|
||||
|
||||
constexpr static int CompleterImageSize = 24;
|
||||
|
||||
class CompleterEntry : public Gtk::ListBoxRow {
|
||||
public:
|
||||
CompleterEntry(Glib::ustring completion, int index);
|
||||
CompleterEntry(const Glib::ustring &completion, int index);
|
||||
void SetTextColor(int color); // SetText will reset
|
||||
void SetText(const Glib::ustring &text);
|
||||
void SetImage(const Glib::RefPtr<Gdk::Pixbuf> &pb);
|
||||
@@ -102,7 +102,7 @@ bool DragListBox::scroll() {
|
||||
}
|
||||
|
||||
void DragListBox::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y, const Gtk::SelectionData &selection_data, guint info, guint time) {
|
||||
int index;
|
||||
int index = 0;
|
||||
if (m_hover_row != nullptr) {
|
||||
if (m_top) {
|
||||
index = m_hover_row->get_index() - 1;
|
||||
@@ -130,7 +130,7 @@ void DragListBox::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &co
|
||||
void DragListBox::add_draggable(Gtk::ListBoxRow *widget) {
|
||||
widget->drag_source_set(m_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
|
||||
widget->signal_drag_begin().connect(sigc::bind<0>(sigc::mem_fun(*this, &DragListBox::row_drag_begin), widget));
|
||||
widget->signal_drag_data_get().connect([widget](const Glib::RefPtr<Gdk::DragContext> &context, Gtk::SelectionData &selection_data, guint info, guint time) {
|
||||
widget->signal_drag_data_get().connect([this, widget](const Glib::RefPtr<Gdk::DragContext> &context, Gtk::SelectionData &selection_data, guint info, guint time) {
|
||||
selection_data.set("GTK_LIST_BOX_ROW", 32, reinterpret_cast<const guint8 *>(&widget), sizeof(&widget));
|
||||
});
|
||||
add(*widget);
|
||||
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() {};
|
||||
};
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "lazyimage.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
LazyImage::LazyImage(int w, int h, bool use_placeholder)
|
||||
: m_width(w)
|
||||
@@ -11,8 +9,8 @@ LazyImage::LazyImage(int w, int h, bool use_placeholder)
|
||||
signal_draw().connect(sigc::mem_fun(*this, &LazyImage::OnDraw));
|
||||
}
|
||||
|
||||
LazyImage::LazyImage(std::string url, int w, int h, bool use_placeholder)
|
||||
: m_url(std::move(url))
|
||||
LazyImage::LazyImage(const std::string &url, int w, int h, bool use_placeholder)
|
||||
: m_url(url)
|
||||
, m_width(w)
|
||||
, m_height(h) {
|
||||
if (use_placeholder)
|
||||
@@ -29,7 +27,7 @@ void LazyImage::SetURL(const std::string &url) {
|
||||
}
|
||||
|
||||
bool LazyImage::OnDraw(const Cairo::RefPtr<Cairo::Context> &context) {
|
||||
if (!m_needs_request || m_url.empty()) return false;
|
||||
if (!m_needs_request || m_url == "") return false;
|
||||
m_needs_request = false;
|
||||
|
||||
if (m_animated) {
|
||||
@@ -5,7 +5,7 @@
|
||||
class LazyImage : public Gtk::Image {
|
||||
public:
|
||||
LazyImage(int w, int h, bool use_placeholder = true);
|
||||
LazyImage(std::string url, int w, int h, bool use_placeholder = true);
|
||||
LazyImage(const std::string &url, int w, int h, bool use_placeholder = true);
|
||||
|
||||
void SetAnimated(bool is_animated);
|
||||
void SetURL(const std::string &url);
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "memberlist.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "util.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "lazyimage.hpp"
|
||||
#include "statusindicator.hpp"
|
||||
|
||||
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));
|
||||
@@ -14,10 +14,10 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
|
||||
m_avatar = Gtk::manage(new LazyImage(16, 16));
|
||||
m_status_indicator = Gtk::manage(new StatusIndicator(ID));
|
||||
|
||||
if (Abaddon::Get().GetSettings().ShowOwnerCrown && guild.has_value() && guild->OwnerID == data.ID) {
|
||||
static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
|
||||
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);
|
||||
@@ -26,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");
|
||||
@@ -39,10 +36,11 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
|
||||
m_label->set_single_line_mode(true);
|
||||
m_label->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
|
||||
static bool show_discriminator = Abaddon::Get().GetSettings().GetShowMemberListDiscriminators();
|
||||
std::string display = data.Username;
|
||||
if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators)
|
||||
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);
|
||||
@@ -92,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,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);
|
||||
@@ -125,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;
|
||||
@@ -151,7 +144,7 @@ void MemberList::UpdateMemberList() {
|
||||
if (!pos_role.has_value()) {
|
||||
roleless_users.push_back(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
pos_to_role[pos_role->Position] = *pos_role;
|
||||
pos_to_users[pos_role->Position].push_back(std::move(*user));
|
||||
@@ -160,18 +153,17 @@ void MemberList::UpdateMemberList() {
|
||||
}
|
||||
|
||||
int num_rows = 0;
|
||||
const auto guild = discord.GetGuild(m_guild_id);
|
||||
if (!guild.has_value()) return;
|
||||
auto add_user = [this, &num_rows, guild](const UserData &data) -> bool {
|
||||
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);
|
||||
return true;
|
||||
};
|
||||
|
||||
auto add_role = [this](const std::string &name) {
|
||||
auto add_role = [this](std::string name) {
|
||||
auto *role_row = Gtk::manage(new Gtk::ListBoxRow);
|
||||
auto *role_lbl = Gtk::manage(new Gtk::Label);
|
||||
|
||||
@@ -216,12 +208,16 @@ void MemberList::UpdateMemberList() {
|
||||
}
|
||||
|
||||
void MemberList::AttachUserMenuHandler(Gtk::ListBoxRow *row, Snowflake id) {
|
||||
row->signal_button_press_event().connect([this, id](GdkEventButton *e) -> bool {
|
||||
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;
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
#include "discord/discord.hpp"
|
||||
#include "../discord/discord.hpp"
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "ratelimitindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
RateLimitIndicator::RateLimitIndicator()
|
||||
@@ -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;
|
||||
|
||||
@@ -66,7 +65,7 @@ int RateLimitIndicator::GetTimeLeft() const {
|
||||
if (sec_diff <= 0)
|
||||
return 0;
|
||||
else
|
||||
return static_cast<int>(sec_diff);
|
||||
return sec_diff;
|
||||
}
|
||||
|
||||
int RateLimitIndicator::GetRateLimit() const {
|
||||
@@ -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
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <gtkmm.h>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include "discord/message.hpp"
|
||||
#include "../discord/message.hpp"
|
||||
|
||||
class RateLimitIndicator : public Gtk::Box {
|
||||
public:
|
||||
@@ -1,7 +1,11 @@
|
||||
#include "statusindicator.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
static const constexpr int Diameter = 8;
|
||||
static const auto OnlineColor = Gdk::RGBA("#43B581");
|
||||
static const auto IdleColor = Gdk::RGBA("#FAA61A");
|
||||
static const auto DNDColor = Gdk::RGBA("#982929");
|
||||
static const auto OfflineColor = Gdk::RGBA("#808080");
|
||||
|
||||
StatusIndicator::StatusIndicator(Snowflake user_id)
|
||||
: Glib::ObjectBase("statusindicator")
|
||||
@@ -14,14 +18,17 @@ 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));
|
||||
|
||||
CheckStatus();
|
||||
}
|
||||
|
||||
StatusIndicator::~StatusIndicator() {
|
||||
}
|
||||
|
||||
void StatusIndicator::CheckStatus() {
|
||||
const auto status = Abaddon::Get().GetDiscordClient().GetUserStatus(m_id);
|
||||
const auto last_status = m_status;
|
||||
@@ -114,7 +121,7 @@ bool StatusIndicator::on_draw(const Cairo::RefPtr<Cairo::Context> &cr) {
|
||||
const auto color = get_style_context()->get_color(Gtk::STATE_FLAG_NORMAL);
|
||||
|
||||
cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue());
|
||||
cr->arc(width / 2.0, height / 2.0, width / 3.0, 0.0, 2 * (4 * std::atan(1)));
|
||||
cr->arc(width / 2, height / 2, width / 3, 0.0, 2 * (4 * std::atan(1)));
|
||||
cr->close_path();
|
||||
cr->fill_preserve();
|
||||
cr->stroke();
|
||||
@@ -1,19 +1,19 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "discord/activity.hpp"
|
||||
#include "../discord/snowflake.hpp"
|
||||
#include "../discord/activity.hpp"
|
||||
|
||||
class StatusIndicator : public Gtk::Widget {
|
||||
public:
|
||||
StatusIndicator(Snowflake user_id);
|
||||
~StatusIndicator() override = default;
|
||||
virtual ~StatusIndicator();
|
||||
|
||||
protected:
|
||||
Gtk::SizeRequestMode get_request_mode_vfunc() const override;
|
||||
void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override;
|
||||
void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override;
|
||||
void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override;
|
||||
void get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const override;
|
||||
void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override;
|
||||
void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override;
|
||||
void on_size_allocate(Gtk::Allocation &allocation) override;
|
||||
void on_map() override;
|
||||
void on_unmap() override;
|
||||
@@ -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 {
|
||||
@@ -101,38 +106,17 @@
|
||||
.message-input, .message-input textview, .message-input textview text {
|
||||
background-color: #242424;
|
||||
color: #adadad;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
border: 1px solid #444444;
|
||||
margin-right: 15px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.message-input.replying {
|
||||
border: 1px solid #026FB9;
|
||||
}
|
||||
|
||||
.message-input.bad-input {
|
||||
border: 1px solid #dd3300;
|
||||
}
|
||||
|
||||
.message-input-browse-icon {
|
||||
color: #b9bbbe;
|
||||
margin-left: 5px;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
/* i dont think theres a way to circumvent having to do this to adjust around the browse icon */
|
||||
.message-input:not(.with-browser-icon) {
|
||||
.message-input {
|
||||
padding: 0px 0px 0px 5px;
|
||||
}
|
||||
|
||||
.message-input.with-browse-icon {
|
||||
padding: 0px 0px 0px 30px;
|
||||
}
|
||||
|
||||
.members {
|
||||
background-color: @background_color;
|
||||
}
|
||||
@@ -157,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;
|
||||
@@ -212,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;
|
||||
}
|
||||
@@ -283,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%);
|
||||
}
|
||||
@@ -294,87 +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;
|
||||
}
|
||||
|
||||
.channel-tab-switcher .box {
|
||||
margin: -7px -1px -7px -1px;
|
||||
background: #2a2a2a;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab:hover {
|
||||
box-shadow: inset 0 -6px #17633e;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab:checked {
|
||||
box-shadow: inset 0 -6px #2feb90;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab {
|
||||
background: #1A1A1A;
|
||||
border: 1px solid #808080;
|
||||
min-height: 35px;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab.needs-attention:not(:checked) {
|
||||
font-weight: bold;
|
||||
animation: 150ms ease-in;
|
||||
/* background-image: radial-gradient(ellipse at bottom, #FF5370, #1A1A1A 30%); */
|
||||
box-shadow: inset 0 -6px red;
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab > button {
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 5px;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
color: #FF5370;
|
||||
background-color: rgba(0.21, 0.21, 0.21, 0.5);
|
||||
}
|
||||
|
||||
.channel-tab-switcher tab > button:hover {
|
||||
background-color: alpha(#ff0000, 0.5);
|
||||
}
|
||||
|
||||
.message-progress {
|
||||
border: none;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
||||
.message-progress trough {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.message-progress progress {
|
||||
border: none;
|
||||
background-color: #dd3300;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.voice-info {
|
||||
background-color: #0B0B0B;
|
||||
padding: 5px;
|
||||
border: 1px solid #202020;
|
||||
}
|
||||
|
||||
.voice-info-disconnect-image {
|
||||
color: #DDDDDD;
|
||||
}
|
||||
|
||||
.voice-info-status {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.voice-info-location {
|
||||
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
ConfirmDialog::ConfirmDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Confirm", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
, m_cancel("Cancel") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
@@ -34,7 +34,3 @@ ConfirmDialog::ConfirmDialog(Gtk::Window &parent)
|
||||
void ConfirmDialog::SetConfirmText(const Glib::ustring &text) {
|
||||
m_label.set_text(text);
|
||||
}
|
||||
|
||||
void ConfirmDialog::SetAcceptOnly(bool accept_only) {
|
||||
m_cancel.set_visible(!accept_only);
|
||||
}
|
||||
@@ -5,7 +5,6 @@ class ConfirmDialog : public Gtk::Dialog {
|
||||
public:
|
||||
ConfirmDialog(Gtk::Window &parent);
|
||||
void SetConfirmText(const Glib::ustring &text);
|
||||
void SetAcceptOnly(bool accept_only);
|
||||
|
||||
protected:
|
||||
Gtk::Label m_label;
|
||||
@@ -3,9 +3,9 @@
|
||||
EditMessageDialog::EditMessageDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Edit Message", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
, m_cancel("Cancel") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "friendpicker.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
FriendPickerDialog::FriendPickerDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Pick a friend", parent, true)
|
||||
@@ -67,7 +67,7 @@ FriendPickerDialogItem::FriendPickerDialogItem(Snowflake user_id)
|
||||
m_name.set_single_line_mode(true);
|
||||
|
||||
m_avatar.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32);
|
||||
if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) {
|
||||
if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations()) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_avatar.property_pixbuf_animation() = pb;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/snowflake.hpp"
|
||||
#include "../discord/snowflake.hpp"
|
||||
|
||||
class FriendPickerDialog : public Gtk::Dialog {
|
||||
public:
|
||||
97
dialogs/joinguild.cpp
Normal file
97
dialogs/joinguild.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "joinguild.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
JoinGuildDialog::JoinGuildDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Join Server", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_info("Enter code") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
|
||||
Glib::signal_idle().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_idle_slot));
|
||||
|
||||
m_entry.signal_changed().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_entry_changed));
|
||||
|
||||
m_ok.set_sensitive(false);
|
||||
|
||||
m_ok.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_OK);
|
||||
});
|
||||
|
||||
m_cancel.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_CANCEL);
|
||||
});
|
||||
|
||||
m_entry.set_hexpand(true);
|
||||
m_layout.add(m_entry);
|
||||
m_lower.set_hexpand(true);
|
||||
m_lower.pack_start(m_info);
|
||||
m_info.set_halign(Gtk::ALIGN_START);
|
||||
m_lower.pack_start(m_ok, Gtk::PACK_SHRINK);
|
||||
m_lower.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||
m_ok.set_halign(Gtk::ALIGN_END);
|
||||
m_cancel.set_halign(Gtk::ALIGN_END);
|
||||
m_layout.add(m_lower);
|
||||
get_content_area()->add(m_layout);
|
||||
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void JoinGuildDialog::on_entry_changed() {
|
||||
std::string s = m_entry.get_text();
|
||||
std::regex invite_regex(R"((https?:\/\/)?discord\.(gg(\/invite)?\/|com\/invite\/)([A-Za-z0-9\-]+))", std::regex_constants::ECMAScript);
|
||||
std::smatch match;
|
||||
bool full_url = std::regex_search(s, match, invite_regex);
|
||||
if (full_url || IsCode(s)) {
|
||||
m_code = full_url ? match[4].str() : s;
|
||||
m_needs_request = true;
|
||||
m_ok.set_sensitive(false);
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinGuildDialog::CheckCode() {
|
||||
auto cb = [this](const std::optional<InviteData> &invite) {
|
||||
if (invite.has_value()) {
|
||||
m_ok.set_sensitive(true);
|
||||
if (invite->Guild.has_value()) {
|
||||
if (invite->MemberCount.has_value())
|
||||
m_info.set_text(invite->Guild->Name + " (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
else
|
||||
m_info.set_text(invite->Guild->Name);
|
||||
} else {
|
||||
m_info.set_text("Group DM (" + std::to_string(*invite->MemberCount) + " members)");
|
||||
}
|
||||
} else {
|
||||
m_ok.set_sensitive(false);
|
||||
m_info.set_text("Invalid invite");
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().FetchInvite(m_code, sigc::track_obj(cb, *this));
|
||||
}
|
||||
|
||||
bool JoinGuildDialog::IsCode(std::string str) {
|
||||
return str.length() >= 2 && std::all_of(str.begin(), str.end(), [](char c) -> bool { return std::isalnum(c) || c == '-'; });
|
||||
}
|
||||
|
||||
std::string JoinGuildDialog::GetCode() {
|
||||
return m_code;
|
||||
}
|
||||
|
||||
static const constexpr int RateLimitMS = 1500;
|
||||
bool JoinGuildDialog::on_idle_slot() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (m_needs_request && ((now - m_last_req_time) > std::chrono::milliseconds(RateLimitMS))) {
|
||||
m_needs_request = false;
|
||||
m_last_req_time = now;
|
||||
CheckCode();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
31
dialogs/joinguild.hpp
Normal file
31
dialogs/joinguild.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
class JoinGuildDialog : public Gtk::Dialog {
|
||||
public:
|
||||
JoinGuildDialog(Gtk::Window &parent);
|
||||
std::string GetCode();
|
||||
|
||||
protected:
|
||||
void on_entry_changed();
|
||||
bool IsCode(std::string str);
|
||||
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Button m_ok;
|
||||
Gtk::Button m_cancel;
|
||||
Gtk::Box m_lower;
|
||||
Gtk::Label m_info;
|
||||
Gtk::Entry m_entry;
|
||||
|
||||
void CheckCode();
|
||||
|
||||
// needs a rate limit cuz if u hit it u get ip banned from /invites for a long time :(
|
||||
bool m_needs_request = false;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_last_req_time;
|
||||
bool on_idle_slot();
|
||||
|
||||
private:
|
||||
std::string m_code;
|
||||
};
|
||||
@@ -3,10 +3,10 @@
|
||||
SetStatusDialog::SetStatusDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Set Status", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_bottom(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
, m_cancel("Cancel") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "discord/objects.hpp"
|
||||
#include "../discord/objects.hpp"
|
||||
|
||||
class SetStatusDialog : public Gtk::Dialog {
|
||||
public:
|
||||
@@ -1,24 +1,17 @@
|
||||
#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)
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
, m_cancel("Cancel") {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -30,8 +23,6 @@ TokenDialog::TokenDialog(Gtk::Window &parent)
|
||||
m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||
m_bbox.set_layout(Gtk::BUTTONBOX_END);
|
||||
|
||||
m_entry.set_input_purpose(Gtk::INPUT_PURPOSE_PASSWORD);
|
||||
m_entry.set_visibility(false);
|
||||
m_entry.set_hexpand(true);
|
||||
m_layout.add(m_entry);
|
||||
m_layout.add(m_bbox);
|
||||
@@ -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:
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "util.hpp"
|
||||
#include "../util.hpp"
|
||||
#include "json.hpp"
|
||||
#include "snowflake.hpp"
|
||||
|
||||
@@ -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;
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
struct BanData {
|
||||
std::string Reason; // null
|
||||
UserData User; // access id
|
||||
UserData User; // access id
|
||||
|
||||
friend void from_json(const nlohmann::json &j, BanData &m);
|
||||
};
|
||||
67
discord/channel.cpp
Normal file
67
discord/channel.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "../abaddon.hpp"
|
||||
#include "channel.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, ChannelData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("type", m.Type);
|
||||
JS_O("guild_id", m.GuildID);
|
||||
JS_O("position", m.Position);
|
||||
JS_O("permission_overwrites", m.PermissionOverwrites);
|
||||
JS_ON("name", m.Name);
|
||||
JS_ON("topic", m.Topic);
|
||||
JS_O("nsfw", m.IsNSFW);
|
||||
JS_ON("last_message_id", m.LastMessageID);
|
||||
JS_O("bitrate", m.Bitrate);
|
||||
JS_O("user_limit", m.UserLimit);
|
||||
JS_O("rate_limit_per_user", m.RateLimitPerUser);
|
||||
JS_O("recipients", m.Recipients);
|
||||
JS_O("recipient_ids", m.RecipientIDs);
|
||||
JS_ON("icon", m.Icon);
|
||||
JS_O("owner_id", m.OwnerID);
|
||||
JS_O("application_id", m.ApplicationID);
|
||||
JS_ON("parent_id", m.ParentID);
|
||||
JS_ON("last_pin_timestamp", m.LastPinTimestamp);
|
||||
}
|
||||
|
||||
void ChannelData::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("type", Type);
|
||||
JS_RD("guild_id", GuildID);
|
||||
JS_RV("position", Position, -1);
|
||||
JS_RD("permission_overwrites", PermissionOverwrites);
|
||||
JS_RD("name", Name);
|
||||
JS_RD("topic", Topic);
|
||||
JS_RD("nsfw", IsNSFW);
|
||||
JS_RD("last_message_id", LastMessageID);
|
||||
JS_RD("bitrate", Bitrate);
|
||||
JS_RD("user_limit", UserLimit);
|
||||
JS_RD("rate_limit_per_user", RateLimitPerUser);
|
||||
JS_RD("recipients", Recipients);
|
||||
JS_RD("icon", Icon);
|
||||
JS_RD("owner_id", OwnerID);
|
||||
JS_RD("application_id", ApplicationID);
|
||||
JS_RD("parent_id", ParentID);
|
||||
JS_RD("last_pin_timestamp", LastPinTimestamp);
|
||||
}
|
||||
|
||||
std::optional<PermissionOverwrite> ChannelData::GetOverwrite(Snowflake id) const {
|
||||
return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id);
|
||||
}
|
||||
|
||||
std::vector<UserData> ChannelData::GetDMRecipients() const {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
if (Recipients.has_value())
|
||||
return *Recipients;
|
||||
|
||||
if (RecipientIDs.has_value()) {
|
||||
std::vector<UserData> ret;
|
||||
for (const auto &id : *RecipientIDs) {
|
||||
auto user = discord.GetUser(id);
|
||||
if (user.has_value())
|
||||
ret.push_back(std::move(*user));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return std::vector<UserData>();
|
||||
}
|
||||
50
discord/channel.hpp
Normal file
50
discord/channel.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "snowflake.hpp"
|
||||
#include "json.hpp"
|
||||
#include "user.hpp"
|
||||
#include "permissions.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum class ChannelType : int {
|
||||
GUILD_TEXT = 0,
|
||||
DM = 1,
|
||||
GUILD_VOICE = 2,
|
||||
GROUP_DM = 3,
|
||||
GUILD_CATEGORY = 4,
|
||||
GUILD_NEWS = 5,
|
||||
GUILD_STORE = 6,
|
||||
/* 7 and 8 were used for LFG */
|
||||
/* 9 and 10 were used for threads */
|
||||
PUBLIC_THREAD = 11,
|
||||
PRIVATE_THREAD = 12,
|
||||
GUILD_STAGE_VOICE = 13,
|
||||
};
|
||||
|
||||
struct ChannelData {
|
||||
Snowflake ID;
|
||||
ChannelType Type;
|
||||
std::optional<Snowflake> GuildID;
|
||||
std::optional<int> Position;
|
||||
std::optional<std::vector<PermissionOverwrite>> PermissionOverwrites; // shouldnt be accessed
|
||||
std::optional<std::string> Name; // null for dm's
|
||||
std::optional<std::string> Topic; // null
|
||||
std::optional<bool> IsNSFW;
|
||||
std::optional<Snowflake> LastMessageID; // null
|
||||
std::optional<int> Bitrate;
|
||||
std::optional<int> UserLimit;
|
||||
std::optional<int> RateLimitPerUser;
|
||||
std::optional<std::vector<UserData>> Recipients; // only access id
|
||||
std::optional<std::vector<Snowflake>> RecipientIDs;
|
||||
std::optional<std::string> Icon; // null
|
||||
std::optional<Snowflake> OwnerID;
|
||||
std::optional<Snowflake> ApplicationID;
|
||||
std::optional<Snowflake> ParentID; // null
|
||||
std::optional<std::string> LastPinTimestamp; // null
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ChannelData &m);
|
||||
void update_from_json(const nlohmann::json &j);
|
||||
|
||||
std::optional<PermissionOverwrite> GetOverwrite(Snowflake id) const;
|
||||
std::vector<UserData> GetDMRecipients() const;
|
||||
};
|
||||
1939
discord/discord.cpp
Normal file
1939
discord/discord.cpp
Normal file
File diff suppressed because it is too large
Load Diff
387
discord/discord.hpp
Normal file
387
discord/discord.hpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#pragma once
|
||||
#include "websocket.hpp"
|
||||
#include "httpclient.hpp"
|
||||
#include "objects.hpp"
|
||||
#include "store.hpp"
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <thread>
|
||||
#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>
|
||||
bool wait_for(std::chrono::duration<R, P> const &time) const {
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
return !cv.wait_for(lock, time, [&] { return terminate; });
|
||||
}
|
||||
|
||||
void kill() {
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
terminate = true;
|
||||
cv.notify_all();
|
||||
}
|
||||
|
||||
void revive() {
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
terminate = false;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::condition_variable cv;
|
||||
mutable std::mutex m;
|
||||
bool terminate = false;
|
||||
};
|
||||
|
||||
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();
|
||||
void Stop();
|
||||
bool IsStarted() const;
|
||||
bool IsStoreValid() const;
|
||||
|
||||
using guilds_type = Store::guilds_type;
|
||||
using channels_type = Store::channels_type;
|
||||
using messages_type = Store::messages_type;
|
||||
using users_type = Store::users_type;
|
||||
using roles_type = Store::roles_type;
|
||||
using members_type = Store::members_type;
|
||||
using permission_overwrites_type = Store::permission_overwrites_type;
|
||||
|
||||
std::unordered_set<Snowflake> GetGuilds() const;
|
||||
const UserData &GetUserData() const;
|
||||
const UserSettings &GetUserSettings() const;
|
||||
std::vector<Snowflake> GetUserSortedGuilds() const;
|
||||
std::set<Snowflake> GetMessagesForChannel(Snowflake id) const;
|
||||
std::set<Snowflake> GetPrivateChannels() const;
|
||||
|
||||
EPremiumType GetSelfPremiumType() const;
|
||||
|
||||
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;
|
||||
std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
|
||||
std::optional<UserData> GetUser(Snowflake id) const;
|
||||
std::optional<RoleData> GetRole(Snowflake id) const;
|
||||
std::optional<GuildData> GetGuild(Snowflake id) const;
|
||||
std::optional<GuildMember> GetMember(Snowflake user_id, Snowflake guild_id) const;
|
||||
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::unordered_set<Snowflake> GetUsersInGuild(Snowflake id) const;
|
||||
std::unordered_set<Snowflake> GetChannelsInGuild(Snowflake id) const;
|
||||
|
||||
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
|
||||
|
||||
bool HasAnyChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
|
||||
bool HasChannelPermission(Snowflake user_id, Snowflake channel_id, Permission perm) const;
|
||||
Permission ComputePermissions(Snowflake member_id, Snowflake guild_id) const;
|
||||
Permission ComputeOverwrites(Permission base, Snowflake member_id, Snowflake channel_id) const;
|
||||
bool CanManageMember(Snowflake guild_id, Snowflake actor, Snowflake target) const; // kick, ban, edit nickname (cant think of a better name)
|
||||
|
||||
void ChatMessageCallback(std::string nonce, const http::response_type &response);
|
||||
|
||||
void SendChatMessage(const std::string &content, Snowflake channel);
|
||||
void SendChatMessage(const std::string &content, Snowflake channel, Snowflake referenced_message);
|
||||
void DeleteMessage(Snowflake channel_id, Snowflake id);
|
||||
void EditMessage(Snowflake channel_id, Snowflake id, std::string content);
|
||||
void SendLazyLoad(Snowflake id);
|
||||
void JoinGuild(std::string code);
|
||||
void LeaveGuild(Snowflake id);
|
||||
void KickUser(Snowflake user_id, Snowflake guild_id);
|
||||
void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages
|
||||
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(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(bool success)> callback);
|
||||
void SetGuildIcon(Snowflake id, const std::string &data);
|
||||
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(bool success)> callback);
|
||||
void DeleteInvite(const std::string &code);
|
||||
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(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;
|
||||
|
||||
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(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) {
|
||||
callback(CheckCode(response, 200));
|
||||
});
|
||||
}
|
||||
|
||||
// FetchGuildBans fetches all bans+reasons via api, this func fetches stored bans (so usually just GUILD_BAN_ADD data)
|
||||
std::vector<BanData> GetBansInGuild(Snowflake guild_id);
|
||||
void FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback);
|
||||
void FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback);
|
||||
|
||||
void FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback);
|
||||
void FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback);
|
||||
|
||||
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
|
||||
|
||||
void FetchGuildEmojis(Snowflake guild_id, sigc::slot<void(std::vector<EmojiData>)> callback);
|
||||
|
||||
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(bool success)> callback);
|
||||
void FetchUserRelationships(Snowflake user_id, sigc::slot<void(std::vector<UserData>)> callback);
|
||||
|
||||
void GetVerificationGateInfo(Snowflake guild_id, sigc::slot<void(std::optional<VerificationGateInfoObject>)> 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::unordered_set<Snowflake> GetRelationships(RelationshipType type) const;
|
||||
|
||||
private:
|
||||
static const constexpr int InflateChunkSize = 0x10000;
|
||||
std::vector<uint8_t> m_compressed_buf;
|
||||
std::vector<uint8_t> m_decompress_buf;
|
||||
z_stream m_zstream;
|
||||
|
||||
void ProcessNewGuild(GuildData &guild);
|
||||
|
||||
void HandleGatewayMessageRaw(std::string str);
|
||||
void HandleGatewayMessage(std::string str);
|
||||
void HandleGatewayHello(const GatewayMessage &msg);
|
||||
void HandleGatewayReady(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildMemberListUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageDeleteBulk(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildMemberUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayPresenceUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayChannelDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayChannelUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayChannelCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildRoleUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildRoleCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildRoleDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageReactionAdd(const GatewayMessage &msg);
|
||||
void HandleGatewayMessageReactionRemove(const GatewayMessage &msg);
|
||||
void HandleGatewayChannelRecipientAdd(const GatewayMessage &msg);
|
||||
void HandleGatewayChannelRecipientRemove(const GatewayMessage &msg);
|
||||
void HandleGatewayTypingStart(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildBanRemove(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
|
||||
void HandleGatewayInviteCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayInviteDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayUserNoteUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildEmojisUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildJoinRequestCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildJoinRequestUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayGuildJoinRequestDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayReadySupplemental(const GatewayMessage &msg);
|
||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
void HeartbeatThread();
|
||||
void SendIdentify();
|
||||
void SendResume();
|
||||
|
||||
void HandleSocketOpen();
|
||||
void HandleSocketClose(uint16_t code);
|
||||
|
||||
bool CheckCode(const http::response_type &r);
|
||||
bool CheckCode(const http::response_type &r, int expected);
|
||||
|
||||
void StoreMessageData(Message &msg);
|
||||
|
||||
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::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;
|
||||
|
||||
Store m_store;
|
||||
HTTPClient m_http;
|
||||
Websocket m_websocket;
|
||||
std::atomic<bool> m_client_connected = false;
|
||||
std::atomic<bool> m_ready_received = false;
|
||||
|
||||
std::unordered_map<std::string, GatewayEvent> m_event_map;
|
||||
void LoadEventMap();
|
||||
|
||||
std::thread m_heartbeat_thread;
|
||||
std::atomic<int> m_last_sequence = -1;
|
||||
std::atomic<int> m_heartbeat_msec = 0;
|
||||
HeartbeatWaiter m_heartbeat_waiter;
|
||||
std::atomic<bool> m_heartbeat_acked = true;
|
||||
|
||||
bool m_reconnecting = false; // reconnecting either to resume or reidentify
|
||||
bool m_wants_resume = false; // reconnecting specifically to resume
|
||||
std::string m_session_id;
|
||||
|
||||
mutable std::mutex m_msg_mutex;
|
||||
Glib::Dispatcher m_msg_dispatch;
|
||||
std::queue<std::string> m_msg_queue;
|
||||
void MessageDispatch();
|
||||
|
||||
mutable std::mutex m_generic_mutex;
|
||||
Glib::Dispatcher m_generic_dispatch;
|
||||
std::queue<std::function<void()>> m_generic_queue;
|
||||
|
||||
// signals
|
||||
public:
|
||||
typedef sigc::signal<void> type_signal_gateway_ready;
|
||||
typedef sigc::signal<void, Message> type_signal_message_create;
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_delete;
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_message_update;
|
||||
typedef sigc::signal<void, Snowflake> type_signal_guild_member_list_update;
|
||||
typedef sigc::signal<void, GuildData> type_signal_guild_create;
|
||||
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, 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
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_role_delete; // guild id, role id
|
||||
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_add;
|
||||
typedef sigc::signal<void, Snowflake, Glib::ustring> type_signal_reaction_remove;
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_typing_start; // user id, channel id
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_member_update; // guild id, user id
|
||||
typedef sigc::signal<void, Snowflake, Snowflake> type_signal_guild_ban_remove; // guild id, user id
|
||||
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, 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, 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> type_signal_connected;
|
||||
|
||||
type_signal_gateway_ready signal_gateway_ready();
|
||||
type_signal_message_create signal_message_create();
|
||||
type_signal_message_delete signal_message_delete();
|
||||
type_signal_message_update signal_message_update();
|
||||
type_signal_guild_member_list_update signal_guild_member_list_update();
|
||||
type_signal_guild_create signal_guild_create(); // structs are complete in this signal
|
||||
type_signal_guild_delete signal_guild_delete();
|
||||
type_signal_channel_delete signal_channel_delete();
|
||||
type_signal_channel_update signal_channel_update();
|
||||
type_signal_channel_create signal_channel_create();
|
||||
type_signal_guild_update signal_guild_update();
|
||||
type_signal_role_update signal_role_update();
|
||||
type_signal_role_create signal_role_create();
|
||||
type_signal_role_delete signal_role_delete();
|
||||
type_signal_reaction_add signal_reaction_add();
|
||||
type_signal_reaction_remove signal_reaction_remove();
|
||||
type_signal_typing_start signal_typing_start();
|
||||
type_signal_guild_member_update signal_guild_member_update();
|
||||
type_signal_guild_ban_remove signal_guild_ban_remove();
|
||||
type_signal_guild_ban_add signal_guild_ban_add();
|
||||
type_signal_invite_create signal_invite_create();
|
||||
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
|
||||
type_signal_presence_update signal_presence_update();
|
||||
type_signal_note_update signal_note_update();
|
||||
type_signal_guild_emojis_update signal_guild_emojis_update();
|
||||
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_message_sent signal_message_sent();
|
||||
type_signal_message_send_fail signal_message_send_fail();
|
||||
type_signal_disconnected signal_disconnected();
|
||||
type_signal_connected signal_connected();
|
||||
|
||||
protected:
|
||||
type_signal_gateway_ready m_signal_gateway_ready;
|
||||
type_signal_message_create m_signal_message_create;
|
||||
type_signal_message_delete m_signal_message_delete;
|
||||
type_signal_message_update m_signal_message_update;
|
||||
type_signal_guild_member_list_update m_signal_guild_member_list_update;
|
||||
type_signal_guild_create m_signal_guild_create;
|
||||
type_signal_guild_delete m_signal_guild_delete;
|
||||
type_signal_channel_delete m_signal_channel_delete;
|
||||
type_signal_channel_update m_signal_channel_update;
|
||||
type_signal_channel_create m_signal_channel_create;
|
||||
type_signal_guild_update m_signal_guild_update;
|
||||
type_signal_role_update m_signal_role_update;
|
||||
type_signal_role_create m_signal_role_create;
|
||||
type_signal_role_delete m_signal_role_delete;
|
||||
type_signal_reaction_add m_signal_reaction_add;
|
||||
type_signal_reaction_remove m_signal_reaction_remove;
|
||||
type_signal_typing_start m_signal_typing_start;
|
||||
type_signal_guild_member_update m_signal_guild_member_update;
|
||||
type_signal_guild_ban_remove m_signal_guild_ban_remove;
|
||||
type_signal_guild_ban_add m_signal_guild_ban_add;
|
||||
type_signal_invite_create m_signal_invite_create;
|
||||
type_signal_invite_delete m_signal_invite_delete;
|
||||
type_signal_presence_update m_signal_presence_update;
|
||||
type_signal_note_update m_signal_note_update;
|
||||
type_signal_guild_emojis_update m_signal_guild_emojis_update;
|
||||
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_message_sent m_signal_message_sent;
|
||||
type_signal_message_send_fail m_signal_message_send_fail;
|
||||
type_signal_disconnected m_signal_disconnected;
|
||||
type_signal_connected m_signal_connected;
|
||||
};
|
||||
@@ -16,7 +16,7 @@ void to_json(nlohmann::json &j, const EmojiData &m) {
|
||||
j["id"] = m.ID;
|
||||
else
|
||||
j["id"] = nullptr;
|
||||
if (!m.Name.empty())
|
||||
if (m.Name != "")
|
||||
j["name"] = m.Name;
|
||||
else
|
||||
j["name"] = nullptr;
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "guild.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildData &m) {
|
||||
JS_D("id", m.ID);
|
||||
@@ -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);
|
||||
@@ -77,7 +76,7 @@ void GuildData::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("owner_id", OwnerID);
|
||||
std::string tmp;
|
||||
JS_RD("permissions", tmp);
|
||||
if (!tmp.empty())
|
||||
if (tmp != "")
|
||||
Permissions = std::stoull(tmp);
|
||||
JS_RD("region", VoiceRegion);
|
||||
JS_RD("afk_channel_id", AFKChannelID);
|
||||
@@ -119,7 +118,7 @@ void GuildData::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("approximate_presence_count", ApproximatePresenceCount);
|
||||
}
|
||||
|
||||
bool GuildData::HasFeature(const std::string &search_feature) const {
|
||||
bool GuildData::HasFeature(const std::string &search_feature) {
|
||||
if (!Features.has_value()) return false;
|
||||
for (const auto &feature : *Features)
|
||||
if (search_feature == feature)
|
||||
@@ -128,17 +127,77 @@ bool GuildData::HasFeature(const std::string &search_feature) const {
|
||||
}
|
||||
|
||||
bool GuildData::HasIcon() const {
|
||||
return !Icon.empty();
|
||||
return Icon != "";
|
||||
}
|
||||
|
||||
bool GuildData::HasAnimatedIcon() const {
|
||||
return HasIcon() && Icon[0] == 'a' && Icon[1] == '_';
|
||||
}
|
||||
|
||||
std::string GuildData::GetIconURL(const std::string &ext, const std::string &size) const {
|
||||
std::string GuildData::GetIconURL(std::string ext, std::string size) const {
|
||||
return "https://cdn.discordapp.com/icons/" + std::to_string(ID) + "/" + Icon + "." + ext + "?size=" + size;
|
||||
}
|
||||
|
||||
std::vector<Snowflake> GuildData::GetSortedChannels(Snowflake ignore) const {
|
||||
std::vector<Snowflake> ret;
|
||||
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto channels = discord.GetChannelsInGuild(ID);
|
||||
|
||||
std::unordered_map<Snowflake, std::vector<ChannelData>> category_to_channels;
|
||||
std::map<int, std::vector<ChannelData>> position_to_categories;
|
||||
std::map<int, std::vector<ChannelData>> orphan_channels;
|
||||
for (const auto &channel_id : channels) {
|
||||
const auto data = discord.GetChannel(channel_id);
|
||||
if (!data->ParentID.has_value() && (data->Type == ChannelType::GUILD_TEXT || data->Type == ChannelType::GUILD_NEWS))
|
||||
orphan_channels[*data->Position].push_back(*data);
|
||||
else if (data->ParentID.has_value() && (data->Type == ChannelType::GUILD_TEXT || data->Type == ChannelType::GUILD_NEWS))
|
||||
category_to_channels[*data->ParentID].push_back(*data);
|
||||
else if (data->Type == ChannelType::GUILD_CATEGORY)
|
||||
position_to_categories[*data->Position].push_back(*data);
|
||||
}
|
||||
|
||||
for (auto &[pos, channels] : orphan_channels) {
|
||||
std::sort(channels.begin(), channels.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
|
||||
return a.ID < b.ID;
|
||||
});
|
||||
for (const auto &chan : channels)
|
||||
ret.push_back(chan.ID);
|
||||
}
|
||||
|
||||
for (auto &[pos, categories] : position_to_categories) {
|
||||
std::sort(categories.begin(), categories.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
|
||||
return a.ID < b.ID;
|
||||
});
|
||||
for (const auto &category : categories) {
|
||||
ret.push_back(category.ID);
|
||||
if (ignore == category.ID) continue; // stupid hack to save me some time
|
||||
auto it = category_to_channels.find(category.ID);
|
||||
if (it == category_to_channels.end()) continue;
|
||||
auto &channels = it->second;
|
||||
std::sort(channels.begin(), channels.end(), [&](const ChannelData &a, const ChannelData &b) -> bool {
|
||||
return a.Position < b.Position;
|
||||
});
|
||||
for (auto &channel : channels) {
|
||||
ret.push_back(channel.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<RoleData> GuildData::FetchRoles() const {
|
||||
if (!Roles.has_value()) return {};
|
||||
std::vector<RoleData> ret;
|
||||
for (const auto thing : *Roles)
|
||||
ret.push_back(*Abaddon::Get().GetDiscordClient().GetRole(thing.ID));
|
||||
std::sort(ret.begin(), ret.end(), [](const RoleData &a, const RoleData &b) -> bool {
|
||||
return a.Position > b.Position;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildApplicationData &m) {
|
||||
JS_D("user_id", m.UserID);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
@@ -16,13 +16,6 @@ enum class GuildApplicationStatus {
|
||||
UNKNOWN,
|
||||
};
|
||||
|
||||
enum class GuildPremiumTier {
|
||||
NONE = 0,
|
||||
TIER_1 = 1,
|
||||
TIER_2 = 2,
|
||||
TIER_3 = 3,
|
||||
};
|
||||
|
||||
struct GuildApplicationData {
|
||||
Snowflake UserID;
|
||||
Snowflake GuildID;
|
||||
@@ -57,7 +50,7 @@ struct GuildData {
|
||||
std::optional<int> VerificationLevel;
|
||||
std::optional<int> DefaultMessageNotifications;
|
||||
std::optional<int> ExplicitContentFilter;
|
||||
std::optional<std::vector<RoleData>> Roles;
|
||||
std::optional<std::vector<RoleData>> Roles; // only access id
|
||||
std::optional<std::vector<EmojiData>> Emojis; // only access id
|
||||
std::optional<std::unordered_set<std::string>> Features;
|
||||
std::optional<int> MFALevel;
|
||||
@@ -80,14 +73,13 @@ struct GuildData {
|
||||
std::optional<std::string> VanityURL; // null
|
||||
std::optional<std::string> Description; // null
|
||||
std::optional<std::string> BannerHash; // null
|
||||
std::optional<GuildPremiumTier> PremiumTier;
|
||||
std::optional<int> PremiumTier;
|
||||
std::optional<int> PremiumSubscriptionCount;
|
||||
std::optional<std::string> PreferredLocale;
|
||||
std::optional<Snowflake> PublicUpdatesChannelID; // null
|
||||
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;
|
||||
@@ -98,8 +90,10 @@ struct GuildData {
|
||||
friend void from_json(const nlohmann::json &j, GuildData &m);
|
||||
void update_from_json(const nlohmann::json &j);
|
||||
|
||||
bool HasFeature(const std::string &feature) const;
|
||||
bool HasFeature(const std::string &feature);
|
||||
bool HasIcon() const;
|
||||
bool HasAnimatedIcon() const;
|
||||
std::string GetIconURL(const std::string &ext = "png", const std::string &size = "32") const;
|
||||
std::string GetIconURL(std::string ext = "png", std::string size = "32") const;
|
||||
std::vector<Snowflake> GetSortedChannels(Snowflake ignore = Snowflake::Invalid) const;
|
||||
std::vector<RoleData> FetchRoles() const; // sorted
|
||||
};
|
||||
@@ -1,39 +1,29 @@
|
||||
#include "httpclient.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
HTTPClient::HTTPClient() {
|
||||
//#define USE_LOCAL_PROXY
|
||||
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 = std::move(agent);
|
||||
m_agent = agent;
|
||||
}
|
||||
|
||||
void HTTPClient::SetAuth(std::string auth) {
|
||||
m_authorization = std::move(auth);
|
||||
m_authorization = auth;
|
||||
}
|
||||
|
||||
void HTTPClient::SetPersistentHeader(std::string name, std::string value) {
|
||||
m_headers.insert_or_assign(std::move(name), std::move(value));
|
||||
}
|
||||
|
||||
void HTTPClient::SetCookie(std::string_view cookie) {
|
||||
m_cookie = cookie;
|
||||
}
|
||||
|
||||
void HTTPClient::MakeDELETE(const std::string &path, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::MakeDELETE(const std::string &path, std::function<void(http::response_type r)> cb) {
|
||||
printf("DELETE %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
|
||||
http::request req(http::REQUEST_DELETE, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@@ -41,16 +31,18 @@ void HTTPClient::MakeDELETE(const std::string &path, const std::function<void(ht
|
||||
}));
|
||||
}
|
||||
|
||||
void HTTPClient::MakePATCH(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::MakePATCH(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
|
||||
printf("PATCH %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_PATCH, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@@ -58,16 +50,18 @@ void HTTPClient::MakePATCH(const std::string &path, const std::string &payload,
|
||||
}));
|
||||
}
|
||||
|
||||
void HTTPClient::MakePOST(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::MakePOST(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
|
||||
printf("POST %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_POST, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@@ -75,17 +69,19 @@ void HTTPClient::MakePOST(const std::string &path, const std::string &payload, c
|
||||
}));
|
||||
}
|
||||
|
||||
void HTTPClient::MakePUT(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::MakePUT(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb) {
|
||||
printf("PUT %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] {
|
||||
http::request req(http::REQUEST_PUT, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_header("Origin", "https://discord.com");
|
||||
if (!payload.empty())
|
||||
if (payload != "")
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
|
||||
req.set_body(payload);
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@@ -93,13 +89,17 @@ void HTTPClient::MakePUT(const std::string &path, const std::string &payload, co
|
||||
}));
|
||||
}
|
||||
|
||||
void HTTPClient::MakeGET(const std::string &path, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::MakeGET(const std::string &path, std::function<void(http::response_type r)> cb) {
|
||||
printf("GET %s\n", path.c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, path, cb] {
|
||||
http::request req(http::REQUEST_GET, m_api_base + path);
|
||||
AddHeaders(req);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
req.set_header("Content-Type", "application/json");
|
||||
req.set_user_agent(m_agent != "" ? m_agent : "Abaddon");
|
||||
#ifdef USE_LOCAL_PROXY
|
||||
req.set_proxy("http://127.0.0.1:8888");
|
||||
req.set_verify_ssl(false);
|
||||
#endif
|
||||
|
||||
auto res = req.execute();
|
||||
|
||||
@@ -107,21 +107,6 @@ void HTTPClient::MakeGET(const std::string &path, const std::function<void(http:
|
||||
}));
|
||||
}
|
||||
|
||||
http::request HTTPClient::CreateRequest(http::EMethod method, std::string path) {
|
||||
http::request req(method, m_api_base + path);
|
||||
req.set_header("Authorization", m_authorization);
|
||||
req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon");
|
||||
return req;
|
||||
}
|
||||
|
||||
void HTTPClient::Execute(http::request &&req, const std::function<void(http::response_type r)> &cb) {
|
||||
printf("%s %s\n", req.get_method(), req.get_url().c_str());
|
||||
m_futures.push_back(std::async(std::launch::async, [this, cb, req = std::move(req)]() mutable {
|
||||
auto res = req.execute();
|
||||
OnResponse(res, cb);
|
||||
}));
|
||||
}
|
||||
|
||||
void HTTPClient::CleanupFutures() {
|
||||
for (auto it = m_futures.begin(); it != m_futures.end();) {
|
||||
if (it->wait_for(std::chrono::seconds(0)) == std::future_status::ready)
|
||||
@@ -138,19 +123,11 @@ void HTTPClient::RunCallbacks() {
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
void HTTPClient::AddHeaders(http::request &r) {
|
||||
for (const auto &[name, val] : m_headers) {
|
||||
r.set_header(name, val);
|
||||
}
|
||||
curl_easy_setopt(r.get_curl(), CURLOPT_COOKIE, m_cookie.c_str());
|
||||
curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, "");
|
||||
}
|
||||
|
||||
void HTTPClient::OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb) {
|
||||
void HTTPClient::OnResponse(const http::response_type &r, std::function<void(http::response_type r)> cb) {
|
||||
CleanupFutures();
|
||||
try {
|
||||
m_mutex.lock();
|
||||
m_queue.push([r, cb] { cb(r); });
|
||||
m_queue.push([this, r, cb] { cb(r); });
|
||||
m_dispatcher.emit();
|
||||
m_mutex.unlock();
|
||||
} catch (const std::exception &e) {
|
||||
37
discord/httpclient.hpp
Normal file
37
discord/httpclient.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <glibmm.h>
|
||||
#include "../http.hpp"
|
||||
|
||||
class HTTPClient {
|
||||
public:
|
||||
HTTPClient(std::string api_base);
|
||||
|
||||
void SetUserAgent(std::string agent);
|
||||
void SetAuth(std::string auth);
|
||||
void MakeDELETE(const std::string &path, std::function<void(http::response_type r)> cb);
|
||||
void MakeGET(const std::string &path, std::function<void(http::response_type r)> cb);
|
||||
void MakePATCH(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
void MakePOST(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
void MakePUT(const std::string &path, const std::string &payload, std::function<void(http::response_type r)> cb);
|
||||
|
||||
private:
|
||||
void OnResponse(const http::response_type &r, std::function<void(http::response_type r)> cb);
|
||||
void CleanupFutures();
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
Glib::Dispatcher m_dispatcher;
|
||||
std::queue<std::function<void()>> m_queue;
|
||||
void RunCallbacks();
|
||||
|
||||
std::vector<std::future<void>> m_futures;
|
||||
std::string m_api_base;
|
||||
std::string m_authorization;
|
||||
std::string m_agent;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "interactions.hpp"
|
||||
#include "json.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, MessageInteractionData &m) {
|
||||
JS_D("id", m.ID);
|
||||
@@ -14,10 +14,10 @@ enum class InteractionType {
|
||||
};
|
||||
|
||||
struct MessageInteractionData {
|
||||
Snowflake ID; // id of the interaction
|
||||
InteractionType Type; // the type of interaction
|
||||
std::string Name; // the name of the ApplicationCommand
|
||||
UserData User; // the user who invoked the interaction
|
||||
Snowflake ID; // id of the interaction
|
||||
InteractionType Type; // the type of interaction
|
||||
std::string Name; // the name of the ApplicationCommand
|
||||
UserData User; // the user who invoked the interaction
|
||||
// undocumented???
|
||||
std::optional<GuildMember> Member; // the member who invoked the interaction (in a guild)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <optional>
|
||||
#include "util.hpp"
|
||||
#include "../util.hpp"
|
||||
|
||||
namespace detail { // more or less because idk what to name this stuff
|
||||
template<typename T>
|
||||
@@ -141,8 +141,8 @@ inline void json_update_optional_nullable_default(const ::nlohmann::json &j, con
|
||||
} while (0)
|
||||
|
||||
// set a json value from a std::optional only if it has a value
|
||||
#define JS_IF(k, v) \
|
||||
do { \
|
||||
if ((v).has_value()) \
|
||||
j[k] = *(v); \
|
||||
#define JS_IF(k, v) \
|
||||
do { \
|
||||
if (v.has_value()) \
|
||||
j[k] = *v; \
|
||||
} while (0)
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "member.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildMember &m) {
|
||||
JS_O("user", m.User);
|
||||
@@ -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 {
|
||||
@@ -19,7 +17,7 @@ std::vector<RoleData> GuildMember::GetSortedRoles() const {
|
||||
for (const auto role_id : Roles) {
|
||||
const auto role = Abaddon::Get().GetDiscordClient().GetRole(role_id);
|
||||
if (!role.has_value()) continue;
|
||||
roles.push_back(*role);
|
||||
roles.push_back(std::move(*role));
|
||||
}
|
||||
|
||||
std::sort(roles.begin(), roles.end(), [](const RoleData &a, const RoleData &b) {
|
||||
@@ -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,19 +8,15 @@
|
||||
|
||||
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;
|
||||
|
||||
[[nodiscard]] std::vector<RoleData> GetSortedRoles() const;
|
||||
std::vector<RoleData> GetSortedRoles() const;
|
||||
|
||||
void update_from_json(const nlohmann::json &j);
|
||||
friend void from_json(const nlohmann::json &j, GuildMember &m);
|
||||
@@ -176,7 +176,7 @@ void to_json(nlohmann::json &j, const MessageApplicationData &m) {
|
||||
j["id"] = m.ID;
|
||||
JS_IF("cover_image", m.CoverImage);
|
||||
j["description"] = m.Description;
|
||||
if (m.Icon.empty())
|
||||
if (m.Icon == "")
|
||||
j["icon"] = nullptr;
|
||||
else
|
||||
j["icon"] = m.Icon;
|
||||
@@ -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) {
|
||||
@@ -230,7 +229,7 @@ void Message::from_json_edited(const nlohmann::json &j) {
|
||||
JS_O("content", Content);
|
||||
JS_O("timestamp", Timestamp);
|
||||
JS_ON("edited_timestamp", EditedTimestamp);
|
||||
if (!EditedTimestamp.empty())
|
||||
if (EditedTimestamp.size() > 0)
|
||||
SetEdited();
|
||||
JS_O("tts", IsTTS);
|
||||
JS_O("mention_everyone", DoesMentionEveryone);
|
||||
@@ -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() {
|
||||
@@ -263,9 +261,3 @@ bool Message::IsDeleted() const {
|
||||
bool Message::IsEdited() const {
|
||||
return m_edited;
|
||||
}
|
||||
|
||||
bool Message::DoesMention(Snowflake id) const noexcept {
|
||||
return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) {
|
||||
return user.ID == id;
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -209,10 +208,8 @@ struct Message {
|
||||
|
||||
void SetDeleted();
|
||||
void SetEdited();
|
||||
[[nodiscard]] bool IsDeleted() const;
|
||||
[[nodiscard]] bool IsEdited() const;
|
||||
|
||||
[[nodiscard]] bool DoesMention(Snowflake id) const noexcept;
|
||||
bool IsDeleted() const;
|
||||
bool IsEdited() const;
|
||||
|
||||
private:
|
||||
bool m_deleted = false;
|
||||
@@ -41,7 +41,7 @@ void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage::MemberItem
|
||||
JS_D("mute", m.IsMuted);
|
||||
JS_D("joined_at", m.JoinedAt);
|
||||
JS_D("deaf", m.IsDefeaned);
|
||||
JS_ON("hoisted_role", m.HoistedRole);
|
||||
JS_N("hoisted_role", m.HoistedRole);
|
||||
JS_ON("premium_since", m.PremiumSince);
|
||||
JS_ON("nick", m.Nickname);
|
||||
JS_ON("presence", m.Presence);
|
||||
@@ -77,7 +77,7 @@ void from_json(const nlohmann::json &j, GuildMemberListUpdateMessage &m) {
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
|
||||
j["op"] = GatewayOp::GuildSubscriptions;
|
||||
j["op"] = GatewayOp::LazyLoadRequest;
|
||||
j["d"] = nlohmann::json::object();
|
||||
j["d"]["guild_id"] = m.GuildID;
|
||||
if (m.Channels.has_value()) {
|
||||
@@ -85,20 +85,14 @@ 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) {
|
||||
j["op"] = GatewayOp::PresenceUpdate;
|
||||
j["op"] = GatewayOp::UpdateStatus;
|
||||
j["d"] = nlohmann::json::object();
|
||||
j["d"]["since"] = m.Since;
|
||||
j["d"]["activities"] = m.Activities;
|
||||
@@ -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";
|
||||
@@ -119,92 +113,6 @@ void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const RequestGuildMembersMessage &m) {
|
||||
j["op"] = GatewayOp::RequestGuildMembers;
|
||||
j["d"] = nlohmann::json::object();
|
||||
j["d"]["guild_id"] = m.GuildID;
|
||||
j["d"]["presences"] = m.Presences;
|
||||
j["d"]["user_ids"] = m.UserIDs;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadStateEntry &m) {
|
||||
JS_ON("mention_count", m.MentionCount);
|
||||
JS_ON("last_message_id", m.LastMessageID);
|
||||
JS_D("id", m.ID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const ReadStateEntry &m) {
|
||||
j["channel_id"] = m.ID;
|
||||
j["message_id"] = m.LastMessageID;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadStateData &m) {
|
||||
JS_ON("version", m.Version);
|
||||
JS_ON("partial", m.IsPartial);
|
||||
JS_ON("entries", m.Entries);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m) {
|
||||
JS_D("muted", m.Muted);
|
||||
JS_D("message_notifications", m.MessageNotifications);
|
||||
JS_D("collapsed", m.Collapsed);
|
||||
JS_D("channel_id", m.ChannelID);
|
||||
JS_N("mute_config", m.MuteConfig);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) {
|
||||
j["channel_id"] = m.ChannelID;
|
||||
j["collapsed"] = m.Collapsed;
|
||||
j["message_notifications"] = m.MessageNotifications;
|
||||
j["mute_config"] = m.MuteConfig;
|
||||
j["muted"] = m.Muted;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MuteConfigData &m) {
|
||||
JS_ON("end_time", m.EndTime);
|
||||
JS_ON("selected_time_window", m.SelectedTimeWindow);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const MuteConfigData &m) {
|
||||
if (m.EndTime.has_value())
|
||||
j["end_time"] = *m.EndTime;
|
||||
else
|
||||
j["end_time"] = nullptr;
|
||||
j["selected_time_window"] = m.SelectedTimeWindow;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) {
|
||||
JS_D("version", m.Version);
|
||||
JS_D("suppress_roles", m.SuppressRoles);
|
||||
JS_D("suppress_everyone", m.SuppressEveryone);
|
||||
JS_D("muted", m.Muted);
|
||||
JS_D("mobile_push", m.MobilePush);
|
||||
JS_D("message_notifications", m.MessageNotifications);
|
||||
JS_D("hide_muted_channels", m.HideMutedChannels);
|
||||
JS_N("guild_id", m.GuildID);
|
||||
JS_D("channel_overrides", m.ChannelOverrides);
|
||||
JS_N("mute_config", m.MuteConfig);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m) {
|
||||
j["channel_overrides"] = m.ChannelOverrides;
|
||||
j["guild_id"] = m.GuildID;
|
||||
j["hide_muted_channels"] = m.HideMutedChannels;
|
||||
j["message_notifications"] = m.MessageNotifications;
|
||||
j["mobile_push"] = m.MobilePush;
|
||||
j["mute_config"] = m.MuteConfig;
|
||||
j["muted"] = m.Muted;
|
||||
j["suppress_everyone"] = m.SuppressEveryone;
|
||||
j["suppress_roles"] = m.SuppressRoles;
|
||||
j["version"] = m.Version;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsData &m) {
|
||||
JS_D("version", m.Version);
|
||||
JS_D("partial", m.IsPartial);
|
||||
JS_D("entries", m.Entries);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadyEventData &m) {
|
||||
JS_D("v", m.GatewayVersion);
|
||||
JS_D("user", m.SelfUser);
|
||||
@@ -218,8 +126,6 @@ void from_json(const nlohmann::json &j, ReadyEventData &m) {
|
||||
JS_ON("merged_members", m.MergedMembers);
|
||||
JS_O("relationships", m.Relationships);
|
||||
JS_O("guild_join_requests", m.GuildJoinRequests);
|
||||
JS_O("read_state", m.ReadState);
|
||||
JS_D("user_guild_settings", m.GuildSettings);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MergedPresence &m) {
|
||||
@@ -233,14 +139,8 @@ void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m) {
|
||||
JS_D("friends", m.Friends);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, SupplementalGuildEntry &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("voice_states", m.VoiceStates);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ReadySupplementalData &m) {
|
||||
JS_D("merged_presences", m.MergedPresences);
|
||||
JS_D("guilds", m.Guilds);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const IdentifyProperties &m) {
|
||||
@@ -257,7 +157,7 @@ void to_json(nlohmann::json &j, const IdentifyProperties &m) {
|
||||
j["referring_domain_current"] = m.ReferringDomainCurrent;
|
||||
j["release_channel"] = m.ReleaseChannel;
|
||||
j["client_build_number"] = m.ClientBuildNumber;
|
||||
if (m.ClientEventSource.empty())
|
||||
if (m.ClientEventSource == "")
|
||||
j["client_event_source"] = nullptr;
|
||||
else
|
||||
j["client_event_source"] = m.ClientEventSource;
|
||||
@@ -268,7 +168,6 @@ void to_json(nlohmann::json &j, const ClientStateProperties &m) {
|
||||
j["highest_last_message_id"] = m.HighestLastMessageID;
|
||||
j["read_state_version"] = m.ReadStateVersion;
|
||||
j["user_guild_settings_version"] = m.UserGuildSettingsVersion;
|
||||
j["user_settings_version"] = m.UserSettingsVersion;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const IdentifyMessage &m) {
|
||||
@@ -297,7 +196,7 @@ void to_json(nlohmann::json &j, const CreateMessageObject &m) {
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const MessageEditObject &m) {
|
||||
if (!m.Content.empty())
|
||||
if (m.Content.size() > 0)
|
||||
j["content"] = m.Content;
|
||||
|
||||
// todo EmbedData to_json
|
||||
@@ -553,136 +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);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MessageAckData &m) {
|
||||
// JS_D("version", m.Version);
|
||||
JS_D("message_id", m.MessageID);
|
||||
JS_D("channel_id", m.ChannelID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const AckBulkData &m) {
|
||||
j["read_states"] = m.ReadStates;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m) {
|
||||
m.Settings = j;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, GuildMembersChunkData &m) {
|
||||
JS_D("members", m.Members);
|
||||
JS_D("guild_id", m.GuildID);
|
||||
}
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m) {
|
||||
j["op"] = GatewayOp::VoiceStateUpdate;
|
||||
if (m.GuildID.has_value())
|
||||
j["d"]["guild_id"] = *m.GuildID;
|
||||
else
|
||||
j["d"]["guild_id"] = nullptr;
|
||||
if (m.ChannelID.has_value())
|
||||
j["d"]["channel_id"] = *m.ChannelID;
|
||||
else
|
||||
j["d"]["channel_id"] = nullptr;
|
||||
j["d"]["self_mute"] = m.SelfMute;
|
||||
j["d"]["self_deaf"] = m.SelfDeaf;
|
||||
j["d"]["self_video"] = m.SelfVideo;
|
||||
// j["d"]["preferred_region"] = m.PreferredRegion;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, VoiceServerUpdateData &m) {
|
||||
JS_D("token", m.Token);
|
||||
JS_D("endpoint", m.Endpoint);
|
||||
JS_ON("guild_id", m.GuildID);
|
||||
JS_ON("channel_id", m.ChannelID);
|
||||
}
|
||||
#endif
|
||||
|
||||
void from_json(const nlohmann::json &j, VoiceState &m) {
|
||||
JS_ON("guild_id", m.GuildID);
|
||||
JS_N("channel_id", m.ChannelID);
|
||||
JS_D("deaf", m.IsDeafened);
|
||||
JS_D("mute", m.IsMuted);
|
||||
JS_D("self_deaf", m.IsSelfDeafened);
|
||||
JS_D("self_mute", m.IsSelfMuted);
|
||||
JS_D("self_video", m.IsSelfVideo);
|
||||
JS_O("self_stream", m.IsSelfStream);
|
||||
JS_D("suppress", m.IsSuppressed);
|
||||
JS_D("user_id", m.UserID);
|
||||
JS_ON("member", m.Member);
|
||||
JS_D("session_id", m.SessionID);
|
||||
}
|
||||
@@ -19,40 +19,20 @@
|
||||
#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
|
||||
|
||||
enum class GatewayOp : int {
|
||||
Dispatch = 0,
|
||||
Event = 0,
|
||||
Heartbeat = 1,
|
||||
Identify = 2,
|
||||
PresenceUpdate = 3,
|
||||
VoiceStateUpdate = 4,
|
||||
VoiceServerPing = 5,
|
||||
UpdateStatus = 3,
|
||||
Resume = 6,
|
||||
Reconnect = 7,
|
||||
RequestGuildMembers = 8,
|
||||
InvalidSession = 9,
|
||||
Hello = 10,
|
||||
HeartbeatAck = 11,
|
||||
// 12 unused
|
||||
CallConnect = 13,
|
||||
GuildSubscriptions = 14,
|
||||
LobbyConnect = 15,
|
||||
LobbyDisconnect = 16,
|
||||
LobbyVoiceStatesUpdate = 17,
|
||||
StreamCreate = 18,
|
||||
StreamDelete = 19,
|
||||
StreamWatch = 20,
|
||||
StreamPing = 21,
|
||||
StreamSetPaused = 22,
|
||||
// 23 unused
|
||||
RequestGuildApplicationCommands = 24,
|
||||
EmbeddedActivityLaunch = 25,
|
||||
EmbeddedActivityClose = 26,
|
||||
EmbeddedActivityUpdate = 27,
|
||||
RequestForumUnreads = 28,
|
||||
LazyLoadRequest = 14,
|
||||
};
|
||||
|
||||
enum class GatewayEvent : int {
|
||||
@@ -88,20 +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,
|
||||
MESSAGE_ACK,
|
||||
USER_GUILD_SETTINGS_UPDATE,
|
||||
GUILD_MEMBERS_CHUNK,
|
||||
VOICE_STATE_UPDATE,
|
||||
VOICE_SERVER_UPDATE,
|
||||
};
|
||||
|
||||
enum class GatewayCloseCode : uint16_t {
|
||||
@@ -199,7 +165,7 @@ struct GuildMemberListUpdateMessage {
|
||||
std::string HoistedRole; // null
|
||||
bool IsDefeaned;
|
||||
|
||||
[[nodiscard]] GuildMember GetAsMemberData() const;
|
||||
GuildMember GetAsMemberData() const;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, MemberItem &m);
|
||||
|
||||
@@ -229,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);
|
||||
};
|
||||
@@ -248,67 +212,6 @@ struct UpdateStatusMessage {
|
||||
friend void to_json(nlohmann::json &j, const UpdateStatusMessage &m);
|
||||
};
|
||||
|
||||
struct RequestGuildMembersMessage {
|
||||
Snowflake GuildID;
|
||||
bool Presences;
|
||||
std::vector<Snowflake> UserIDs;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const RequestGuildMembersMessage &m);
|
||||
};
|
||||
|
||||
struct ReadStateEntry {
|
||||
int MentionCount;
|
||||
Snowflake LastMessageID;
|
||||
Snowflake ID;
|
||||
// std::string LastPinTimestamp; iso
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ReadStateEntry &m);
|
||||
friend void to_json(nlohmann::json &j, const ReadStateEntry &m);
|
||||
};
|
||||
|
||||
struct ReadStateData {
|
||||
int Version;
|
||||
bool IsPartial;
|
||||
std::vector<ReadStateEntry> Entries;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ReadStateData &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsChannelOverride {
|
||||
bool Muted;
|
||||
MuteConfigData MuteConfig;
|
||||
int MessageNotifications;
|
||||
bool Collapsed;
|
||||
Snowflake ChannelID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m);
|
||||
friend void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsEntry {
|
||||
int Version;
|
||||
bool SuppressRoles;
|
||||
bool SuppressEveryone;
|
||||
bool Muted;
|
||||
MuteConfigData MuteConfig;
|
||||
bool MobilePush;
|
||||
int MessageNotifications;
|
||||
bool HideMutedChannels;
|
||||
Snowflake GuildID;
|
||||
std::vector<UserGuildSettingsChannelOverride> ChannelOverrides;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m);
|
||||
friend void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsData {
|
||||
int Version;
|
||||
bool IsPartial;
|
||||
std::vector<UserGuildSettingsEntry> Entries;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsData &m);
|
||||
};
|
||||
|
||||
struct ReadyEventData {
|
||||
int GatewayVersion;
|
||||
UserData SelfUser;
|
||||
@@ -324,8 +227,6 @@ struct ReadyEventData {
|
||||
std::optional<std::vector<std::vector<GuildMember>>> MergedMembers;
|
||||
std::optional<std::vector<RelationshipData>> Relationships;
|
||||
std::optional<std::vector<GuildApplicationData>> GuildJoinRequests;
|
||||
ReadStateData ReadState;
|
||||
UserGuildSettingsData GuildSettings;
|
||||
// std::vector<Unknown> ConnectedAccounts; // opt
|
||||
// std::map<std::string, Unknown> Consents; // opt
|
||||
// std::vector<Unknown> Experiments; // opt
|
||||
@@ -354,18 +255,8 @@ struct SupplementalMergedPresencesData {
|
||||
friend void from_json(const nlohmann::json &j, SupplementalMergedPresencesData &m);
|
||||
};
|
||||
|
||||
struct VoiceState;
|
||||
struct SupplementalGuildEntry {
|
||||
// std::vector<?> EmbeddedActivities;
|
||||
Snowflake ID;
|
||||
std::vector<VoiceState> VoiceStates;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, SupplementalGuildEntry &m);
|
||||
};
|
||||
|
||||
struct ReadySupplementalData {
|
||||
SupplementalMergedPresencesData MergedPresences;
|
||||
std::vector<SupplementalGuildEntry> Guilds;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ReadySupplementalData &m);
|
||||
};
|
||||
@@ -394,7 +285,6 @@ struct ClientStateProperties {
|
||||
std::string HighestLastMessageID = "0";
|
||||
int ReadStateVersion = 0;
|
||||
int UserGuildSettingsVersion = -1;
|
||||
int UserSettingsVersion = -1;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const ClientStateProperties &m);
|
||||
};
|
||||
@@ -736,182 +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);
|
||||
};
|
||||
|
||||
struct MessageAckData {
|
||||
// int Version; // what is this ?!?!?!!?
|
||||
Snowflake MessageID;
|
||||
Snowflake ChannelID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, MessageAckData &m);
|
||||
};
|
||||
|
||||
struct AckBulkData {
|
||||
std::vector<ReadStateEntry> ReadStates;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const AckBulkData &m);
|
||||
};
|
||||
|
||||
struct UserGuildSettingsUpdateData {
|
||||
UserGuildSettingsEntry Settings;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m);
|
||||
};
|
||||
|
||||
struct GuildMembersChunkData {
|
||||
/*
|
||||
not needed so not deserialized
|
||||
int ChunkCount;
|
||||
int ChunkIndex;
|
||||
std::vector<?> NotFound;
|
||||
*/
|
||||
Snowflake GuildID;
|
||||
std::vector<GuildMember> Members;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, GuildMembersChunkData &m);
|
||||
};
|
||||
|
||||
#ifdef WITH_VOICE
|
||||
struct VoiceStateUpdateMessage {
|
||||
std::optional<Snowflake> GuildID;
|
||||
std::optional<Snowflake> ChannelID;
|
||||
bool SelfMute = false;
|
||||
bool SelfDeaf = false;
|
||||
bool SelfVideo = false;
|
||||
std::string PreferredRegion;
|
||||
|
||||
friend void to_json(nlohmann::json &j, const VoiceStateUpdateMessage &m);
|
||||
};
|
||||
|
||||
struct VoiceServerUpdateData {
|
||||
std::string Token;
|
||||
std::string Endpoint;
|
||||
std::optional<Snowflake> GuildID;
|
||||
std::optional<Snowflake> ChannelID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, VoiceServerUpdateData &m);
|
||||
};
|
||||
#endif
|
||||
|
||||
struct VoiceState {
|
||||
std::optional<Snowflake> ChannelID;
|
||||
bool IsDeafened;
|
||||
bool IsMuted;
|
||||
std::optional<Snowflake> GuildID;
|
||||
std::optional<GuildMember> Member;
|
||||
bool IsSelfDeafened;
|
||||
bool IsSelfMuted;
|
||||
bool IsSelfVideo;
|
||||
bool IsSelfStream = false;
|
||||
std::string SessionID;
|
||||
bool IsSuppressed;
|
||||
Snowflake UserID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, VoiceState &m);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user