summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml14
-rw-r--r--.gitmodules9
-rw-r--r--CMakeLists.txt33
-rw-r--r--README.md15
-rw-r--r--cmake/Findsimpleini.cmake15
-rw-r--r--discord/store.cpp1507
-rw-r--r--discord/store.hpp172
-rw-r--r--res/css/application-low-priority.css (renamed from css/application-low-priority.css)0
-rw-r--r--res/css/bare.css (renamed from css/bare.css)0
-rw-r--r--res/css/main.css (renamed from css/main.css)0
-rw-r--r--res/fonts/TwitterColorEmoji.ttf (renamed from fonts/TwitterColorEmoji.ttf)bin7656676 -> 7656676 bytes
-rw-r--r--res/fonts/conf.d/10-autohint.conf (renamed from fonts/conf.d/10-autohint.conf)0
-rw-r--r--res/fonts/conf.d/10-hinting-full.conf (renamed from fonts/conf.d/10-hinting-full.conf)0
-rw-r--r--res/fonts/conf.d/10-hinting-medium.conf (renamed from fonts/conf.d/10-hinting-medium.conf)0
-rw-r--r--res/fonts/conf.d/10-hinting-none.conf (renamed from fonts/conf.d/10-hinting-none.conf)0
-rw-r--r--res/fonts/conf.d/10-hinting-slight.conf (renamed from fonts/conf.d/10-hinting-slight.conf)0
-rw-r--r--res/fonts/conf.d/10-no-sub-pixel.conf (renamed from fonts/conf.d/10-no-sub-pixel.conf)0
-rw-r--r--res/fonts/conf.d/10-scale-bitmap-fonts.conf (renamed from fonts/conf.d/10-scale-bitmap-fonts.conf)0
-rw-r--r--res/fonts/conf.d/10-sub-pixel-bgr.conf (renamed from fonts/conf.d/10-sub-pixel-bgr.conf)0
-rw-r--r--res/fonts/conf.d/10-sub-pixel-rgb.conf (renamed from fonts/conf.d/10-sub-pixel-rgb.conf)0
-rw-r--r--res/fonts/conf.d/10-sub-pixel-vbgr.conf (renamed from fonts/conf.d/10-sub-pixel-vbgr.conf)0
-rw-r--r--res/fonts/conf.d/10-sub-pixel-vrgb.conf (renamed from fonts/conf.d/10-sub-pixel-vrgb.conf)0
-rw-r--r--res/fonts/conf.d/10-unhinted.conf (renamed from fonts/conf.d/10-unhinted.conf)0
-rw-r--r--res/fonts/conf.d/11-lcdfilter-default.conf (renamed from fonts/conf.d/11-lcdfilter-default.conf)0
-rw-r--r--res/fonts/conf.d/11-lcdfilter-legacy.conf (renamed from fonts/conf.d/11-lcdfilter-legacy.conf)0
-rw-r--r--res/fonts/conf.d/11-lcdfilter-light.conf (renamed from fonts/conf.d/11-lcdfilter-light.conf)0
-rw-r--r--res/fonts/conf.d/20-unhint-small-vera.conf (renamed from fonts/conf.d/20-unhint-small-vera.conf)0
-rw-r--r--res/fonts/conf.d/25-unhint-nonlatin.conf (renamed from fonts/conf.d/25-unhint-nonlatin.conf)0
-rw-r--r--res/fonts/conf.d/30-metric-aliases.conf (renamed from fonts/conf.d/30-metric-aliases.conf)0
-rw-r--r--res/fonts/conf.d/30-urw-aliases.conf (renamed from fonts/conf.d/30-urw-aliases.conf)0
-rw-r--r--res/fonts/conf.d/40-nonlatin.conf (renamed from fonts/conf.d/40-nonlatin.conf)0
-rw-r--r--res/fonts/conf.d/45-latin.conf (renamed from fonts/conf.d/45-latin.conf)0
-rw-r--r--res/fonts/conf.d/49-sansserif.conf (renamed from fonts/conf.d/49-sansserif.conf)0
-rw-r--r--res/fonts/conf.d/50-user.conf (renamed from fonts/conf.d/50-user.conf)0
-rw-r--r--res/fonts/conf.d/51-local.conf (renamed from fonts/conf.d/51-local.conf)0
-rw-r--r--res/fonts/conf.d/55-emoji-prepend.conf (renamed from fonts/conf.d/55-emoji-prepend.conf)0
-rw-r--r--res/fonts/conf.d/60-latin.conf (renamed from fonts/conf.d/60-latin.conf)0
-rw-r--r--res/fonts/conf.d/65-fonts-persian.conf (renamed from fonts/conf.d/65-fonts-persian.conf)0
-rw-r--r--res/fonts/conf.d/65-khmer.conf (renamed from fonts/conf.d/65-khmer.conf)0
-rw-r--r--res/fonts/conf.d/65-nonlatin.conf (renamed from fonts/conf.d/65-nonlatin.conf)0
-rw-r--r--res/fonts/conf.d/69-unifont.conf (renamed from fonts/conf.d/69-unifont.conf)0
-rw-r--r--res/fonts/conf.d/70-yes-bitmaps.conf (renamed from fonts/conf.d/70-yes-bitmaps.conf)0
-rw-r--r--res/fonts/conf.d/80-delicious.conf (renamed from fonts/conf.d/80-delicious.conf)0
-rw-r--r--res/fonts/conf.d/90-synthetic.conf (renamed from fonts/conf.d/90-synthetic.conf)0
-rw-r--r--res/fonts/fonts.template.conf (renamed from fonts/fonts.template.conf)0
-rw-r--r--res/res/battlenet.png (renamed from res/battlenet.png)bin8129 -> 8129 bytes
-rw-r--r--res/res/certifiedmoderator.png (renamed from res/certifiedmoderator.png)bin7145 -> 7145 bytes
-rw-r--r--res/res/checkmark.png (renamed from res/checkmark.png)bin49387 -> 49387 bytes
-rw-r--r--res/res/clock.png (renamed from res/clock.png)bin3525 -> 3525 bytes
-rw-r--r--res/res/crown.png (renamed from res/crown.png)bin6610 -> 6610 bytes
-rw-r--r--res/res/decamarks.png (renamed from res/decamarks.png)bin1926 -> 1926 bytes
-rw-r--r--res/res/discordbughunter.png (renamed from res/discordbughunter.png)bin6723 -> 6723 bytes
-rw-r--r--res/res/discordbughunter2.png (renamed from res/discordbughunter2.png)bin7039 -> 7039 bytes
-rw-r--r--res/res/discordstaff.png (renamed from res/discordstaff.png)bin8447 -> 8447 bytes
-rw-r--r--res/res/earlysupporter.png (renamed from res/earlysupporter.png)bin8969 -> 8969 bytes
-rw-r--r--res/res/earlyverifiedbotdeveloper.png (renamed from res/earlyverifiedbotdeveloper.png)bin7217 -> 7217 bytes
-rw-r--r--res/res/emojis.bin (renamed from res/emojis.bin)bin3434074 -> 3434074 bytes
-rw-r--r--res/res/facebook.png (renamed from res/facebook.png)bin2304 -> 2304 bytes
-rw-r--r--res/res/github.png (renamed from res/github.png)bin6700 -> 6700 bytes
-rw-r--r--res/res/guildsubscriber.png (renamed from res/guildsubscriber.png)bin4371 -> 4371 bytes
-rw-r--r--res/res/hypesquadbalance.png (renamed from res/hypesquadbalance.png)bin7220 -> 7220 bytes
-rw-r--r--res/res/hypesquadbravery.png (renamed from res/hypesquadbravery.png)bin7427 -> 7427 bytes
-rw-r--r--res/res/hypesquadbrilliance.png (renamed from res/hypesquadbrilliance.png)bin7427 -> 7427 bytes
-rw-r--r--res/res/hypesquadevents.png (renamed from res/hypesquadevents.png)bin6789 -> 6789 bytes
-rw-r--r--res/res/leagueoflegends.png (renamed from res/leagueoflegends.png)bin3508 -> 3508 bytes
-rw-r--r--res/res/partneredowner.png (renamed from res/partneredowner.png)bin5302 -> 5302 bytes
-rw-r--r--res/res/premium.png (renamed from res/premium.png)bin7118 -> 7118 bytes
-rw-r--r--res/res/reddit.png (renamed from res/reddit.png)bin6438 -> 6438 bytes
-rw-r--r--res/res/skype.png (renamed from res/skype.png)bin6775 -> 6775 bytes
-rw-r--r--res/res/spotify.png (renamed from res/spotify.png)bin4925 -> 4925 bytes
-rw-r--r--res/res/steam.png (renamed from res/steam.png)bin7088 -> 7088 bytes
-rw-r--r--res/res/twitch.png (renamed from res/twitch.png)bin1786 -> 1786 bytes
-rw-r--r--res/res/twitter.png (renamed from res/twitter.png)bin3713 -> 3713 bytes
-rw-r--r--res/res/typing_indicator.gif (renamed from res/typing_indicator.gif)bin4218 -> 4218 bytes
-rw-r--r--res/res/xbox.png (renamed from res/xbox.png)bin10736 -> 10736 bytes
-rw-r--r--res/res/youtube.png (renamed from res/youtube.png)bin3386 -> 3386 bytes
-rw-r--r--settings.cpp115
-rw-r--r--settings.hpp62
-rw-r--r--src/MurmurHash3.cpp (renamed from MurmurHash3.cpp)0
-rw-r--r--src/MurmurHash3.h (renamed from MurmurHash3.h)0
-rw-r--r--src/abaddon.cpp (renamed from abaddon.cpp)86
-rw-r--r--src/abaddon.hpp (renamed from abaddon.hpp)5
-rw-r--r--src/components/cellrendererpixbufanimation.cpp (renamed from components/cellrendererpixbufanimation.cpp)0
-rw-r--r--src/components/cellrendererpixbufanimation.hpp (renamed from components/cellrendererpixbufanimation.hpp)0
-rw-r--r--src/components/channels.cpp (renamed from components/channels.cpp)20
-rw-r--r--src/components/channels.hpp (renamed from components/channels.hpp)0
-rw-r--r--src/components/chatinput.cpp (renamed from components/chatinput.cpp)0
-rw-r--r--src/components/chatinput.hpp (renamed from components/chatinput.hpp)0
-rw-r--r--src/components/chatinputindicator.cpp (renamed from components/chatinputindicator.cpp)4
-rw-r--r--src/components/chatinputindicator.hpp (renamed from components/chatinputindicator.hpp)4
-rw-r--r--src/components/chatlist.cpp (renamed from components/chatlist.cpp)0
-rw-r--r--src/components/chatlist.hpp (renamed from components/chatlist.hpp)0
-rw-r--r--src/components/chatmessage.cpp (renamed from components/chatmessage.cpp)49
-rw-r--r--src/components/chatmessage.hpp (renamed from components/chatmessage.hpp)2
-rw-r--r--src/components/chatwindow.cpp (renamed from components/chatwindow.cpp)2
-rw-r--r--src/components/chatwindow.hpp (renamed from components/chatwindow.hpp)2
-rw-r--r--src/components/completer.cpp (renamed from components/completer.cpp)4
-rw-r--r--src/components/completer.hpp (renamed from components/completer.hpp)2
-rw-r--r--src/components/draglistbox.cpp (renamed from components/draglistbox.cpp)0
-rw-r--r--src/components/draglistbox.hpp (renamed from components/draglistbox.hpp)0
-rw-r--r--src/components/friendslist.cpp (renamed from components/friendslist.cpp)5
-rw-r--r--src/components/friendslist.hpp (renamed from components/friendslist.hpp)2
-rw-r--r--src/components/lazyimage.cpp (renamed from components/lazyimage.cpp)2
-rw-r--r--src/components/lazyimage.hpp (renamed from components/lazyimage.hpp)0
-rw-r--r--src/components/memberlist.cpp (renamed from components/memberlist.cpp)10
-rw-r--r--src/components/memberlist.hpp (renamed from components/memberlist.hpp)2
-rw-r--r--src/components/ratelimitindicator.cpp (renamed from components/ratelimitindicator.cpp)2
-rw-r--r--src/components/ratelimitindicator.hpp (renamed from components/ratelimitindicator.hpp)2
-rw-r--r--src/components/statusindicator.cpp (renamed from components/statusindicator.cpp)2
-rw-r--r--src/components/statusindicator.hpp (renamed from components/statusindicator.hpp)4
-rw-r--r--src/config.h.in (renamed from config.h.in)0
-rw-r--r--src/constants.hpp (renamed from constants.hpp)0
-rw-r--r--src/dialogs/confirm.cpp (renamed from dialogs/confirm.cpp)0
-rw-r--r--src/dialogs/confirm.hpp (renamed from dialogs/confirm.hpp)0
-rw-r--r--src/dialogs/editmessage.cpp (renamed from dialogs/editmessage.cpp)0
-rw-r--r--src/dialogs/editmessage.hpp (renamed from dialogs/editmessage.hpp)0
-rw-r--r--src/dialogs/friendpicker.cpp (renamed from dialogs/friendpicker.cpp)4
-rw-r--r--src/dialogs/friendpicker.hpp (renamed from dialogs/friendpicker.hpp)2
-rw-r--r--src/dialogs/joinguild.cpp (renamed from dialogs/joinguild.cpp)2
-rw-r--r--src/dialogs/joinguild.hpp (renamed from dialogs/joinguild.hpp)0
-rw-r--r--src/dialogs/setstatus.cpp (renamed from dialogs/setstatus.cpp)0
-rw-r--r--src/dialogs/setstatus.hpp (renamed from dialogs/setstatus.hpp)2
-rw-r--r--src/dialogs/token.cpp (renamed from dialogs/token.cpp)0
-rw-r--r--src/dialogs/token.hpp (renamed from dialogs/token.hpp)0
-rw-r--r--src/dialogs/verificationgate.cpp (renamed from dialogs/verificationgate.cpp)2
-rw-r--r--src/dialogs/verificationgate.hpp (renamed from dialogs/verificationgate.hpp)2
-rw-r--r--src/discord/activity.cpp (renamed from discord/activity.cpp)0
-rw-r--r--src/discord/activity.hpp (renamed from discord/activity.hpp)2
-rw-r--r--src/discord/auditlog.cpp (renamed from discord/auditlog.cpp)0
-rw-r--r--src/discord/auditlog.hpp (renamed from discord/auditlog.hpp)0
-rw-r--r--src/discord/ban.cpp (renamed from discord/ban.cpp)0
-rw-r--r--src/discord/ban.hpp (renamed from discord/ban.hpp)0
-rw-r--r--src/discord/channel.cpp (renamed from discord/channel.cpp)2
-rw-r--r--src/discord/channel.hpp (renamed from discord/channel.hpp)0
-rw-r--r--src/discord/discord.cpp (renamed from discord/discord.cpp)122
-rw-r--r--src/discord/discord.hpp (renamed from discord/discord.hpp)11
-rw-r--r--src/discord/emoji.cpp (renamed from discord/emoji.cpp)0
-rw-r--r--src/discord/emoji.hpp (renamed from discord/emoji.hpp)0
-rw-r--r--src/discord/errors.hpp (renamed from discord/errors.hpp)0
-rw-r--r--src/discord/guild.cpp (renamed from discord/guild.cpp)14
-rw-r--r--src/discord/guild.hpp (renamed from discord/guild.hpp)3
-rw-r--r--src/discord/httpclient.cpp (renamed from discord/httpclient.cpp)0
-rw-r--r--src/discord/httpclient.hpp (renamed from discord/httpclient.hpp)2
-rw-r--r--src/discord/interactions.cpp (renamed from discord/interactions.cpp)2
-rw-r--r--src/discord/interactions.hpp (renamed from discord/interactions.hpp)0
-rw-r--r--src/discord/invite.cpp (renamed from discord/invite.cpp)0
-rw-r--r--src/discord/invite.hpp (renamed from discord/invite.hpp)0
-rw-r--r--src/discord/json.hpp (renamed from discord/json.hpp)2
-rw-r--r--src/discord/member.cpp (renamed from discord/member.cpp)2
-rw-r--r--src/discord/member.hpp (renamed from discord/member.hpp)0
-rw-r--r--src/discord/message.cpp (renamed from discord/message.cpp)0
-rw-r--r--src/discord/message.hpp (renamed from discord/message.hpp)0
-rw-r--r--src/discord/objects.cpp (renamed from discord/objects.cpp)0
-rw-r--r--src/discord/objects.hpp (renamed from discord/objects.hpp)0
-rw-r--r--src/discord/permissions.cpp (renamed from discord/permissions.cpp)0
-rw-r--r--src/discord/permissions.hpp (renamed from discord/permissions.hpp)4
-rw-r--r--src/discord/relationship.cpp (renamed from discord/relationship.cpp)0
-rw-r--r--src/discord/relationship.hpp (renamed from discord/relationship.hpp)0
-rw-r--r--src/discord/role.cpp (renamed from discord/role.cpp)0
-rw-r--r--src/discord/role.hpp (renamed from discord/role.hpp)0
-rw-r--r--src/discord/snowflake.cpp (renamed from discord/snowflake.cpp)0
-rw-r--r--src/discord/snowflake.hpp (renamed from discord/snowflake.hpp)0
-rw-r--r--src/discord/sticker.cpp (renamed from discord/sticker.cpp)0
-rw-r--r--src/discord/sticker.hpp (renamed from discord/sticker.hpp)0
-rw-r--r--src/discord/store.cpp2255
-rw-r--r--src/discord/store.hpp304
-rw-r--r--src/discord/user.cpp (renamed from discord/user.cpp)2
-rw-r--r--src/discord/user.hpp (renamed from discord/user.hpp)0
-rw-r--r--src/discord/usersettings.cpp (renamed from discord/usersettings.cpp)0
-rw-r--r--src/discord/usersettings.hpp (renamed from discord/usersettings.hpp)0
-rw-r--r--src/discord/webhook.cpp (renamed from discord/webhook.cpp)0
-rw-r--r--src/discord/webhook.hpp (renamed from discord/webhook.hpp)0
-rw-r--r--src/discord/websocket.cpp (renamed from discord/websocket.cpp)11
-rw-r--r--src/discord/websocket.hpp (renamed from discord/websocket.hpp)11
-rw-r--r--src/emojis.cpp (renamed from emojis.cpp)0
-rw-r--r--src/emojis.hpp (renamed from emojis.hpp)0
-rw-r--r--src/filecache.cpp (renamed from filecache.cpp)2
-rw-r--r--src/filecache.hpp (renamed from filecache.hpp)0
-rw-r--r--src/http.cpp (renamed from http.cpp)0
-rw-r--r--src/http.hpp (renamed from http.hpp)0
-rw-r--r--src/imgmanager.cpp (renamed from imgmanager.cpp)0
-rw-r--r--src/imgmanager.hpp (renamed from imgmanager.hpp)0
-rw-r--r--src/platform.cpp (renamed from platform.cpp)0
-rw-r--r--src/platform.hpp (renamed from platform.hpp)0
-rw-r--r--src/settings.cpp115
-rw-r--r--src/settings.hpp55
-rw-r--r--src/state.cpp (renamed from state.cpp)0
-rw-r--r--src/state.hpp (renamed from state.hpp)0
-rw-r--r--src/util.cpp (renamed from util.cpp)0
-rw-r--r--src/util.hpp (renamed from util.hpp)0
-rw-r--r--src/windows/guildsettings/auditlogpane.cpp (renamed from windows/guildsettings/auditlogpane.cpp)2
-rw-r--r--src/windows/guildsettings/auditlogpane.hpp (renamed from windows/guildsettings/auditlogpane.hpp)2
-rw-r--r--src/windows/guildsettings/banspane.cpp (renamed from windows/guildsettings/banspane.cpp)2
-rw-r--r--src/windows/guildsettings/banspane.hpp (renamed from windows/guildsettings/banspane.hpp)4
-rw-r--r--src/windows/guildsettings/emojispane.cpp (renamed from windows/guildsettings/emojispane.cpp)7
-rw-r--r--src/windows/guildsettings/emojispane.hpp (renamed from windows/guildsettings/emojispane.hpp)2
-rw-r--r--src/windows/guildsettings/infopane.cpp (renamed from windows/guildsettings/infopane.cpp)4
-rw-r--r--src/windows/guildsettings/infopane.hpp (renamed from windows/guildsettings/infopane.hpp)2
-rw-r--r--src/windows/guildsettings/invitespane.cpp (renamed from windows/guildsettings/invitespane.cpp)2
-rw-r--r--src/windows/guildsettings/invitespane.hpp (renamed from windows/guildsettings/invitespane.hpp)2
-rw-r--r--src/windows/guildsettings/memberspane.cpp (renamed from windows/guildsettings/memberspane.cpp)14
-rw-r--r--src/windows/guildsettings/memberspane.hpp (renamed from windows/guildsettings/memberspane.hpp)6
-rw-r--r--src/windows/guildsettings/rolespane.cpp (renamed from windows/guildsettings/rolespane.cpp)27
-rw-r--r--src/windows/guildsettings/rolespane.hpp (renamed from windows/guildsettings/rolespane.hpp)4
-rw-r--r--src/windows/guildsettingswindow.cpp (renamed from windows/guildsettingswindow.cpp)2
-rw-r--r--src/windows/guildsettingswindow.hpp (renamed from windows/guildsettingswindow.hpp)2
-rw-r--r--src/windows/mainwindow.cpp (renamed from windows/mainwindow.cpp)20
-rw-r--r--src/windows/mainwindow.hpp (renamed from windows/mainwindow.hpp)8
-rw-r--r--src/windows/pinnedwindow.cpp (renamed from windows/pinnedwindow.cpp)2
-rw-r--r--src/windows/pinnedwindow.hpp (renamed from windows/pinnedwindow.hpp)8
-rw-r--r--src/windows/profile/mutualfriendspane.cpp (renamed from windows/profile/mutualfriendspane.cpp)5
-rw-r--r--src/windows/profile/mutualfriendspane.hpp (renamed from windows/profile/mutualfriendspane.hpp)2
-rw-r--r--src/windows/profile/mutualguildspane.cpp (renamed from windows/profile/mutualguildspane.cpp)5
-rw-r--r--src/windows/profile/mutualguildspane.hpp (renamed from windows/profile/mutualguildspane.hpp)2
-rw-r--r--src/windows/profile/userinfopane.cpp (renamed from windows/profile/userinfopane.cpp)2
-rw-r--r--src/windows/profile/userinfopane.hpp (renamed from windows/profile/userinfopane.hpp)2
-rw-r--r--src/windows/profilewindow.cpp (renamed from windows/profilewindow.cpp)5
-rw-r--r--src/windows/profilewindow.hpp (renamed from windows/profilewindow.hpp)2
-rw-r--r--src/windows/threadswindow.cpp (renamed from windows/threadswindow.cpp)7
-rw-r--r--src/windows/threadswindow.hpp (renamed from windows/threadswindow.hpp)4
m---------subprojects/ixwebsocket (renamed from thirdparty/IXWebSocket)0
m---------thirdparty/simpleini0
222 files changed, 3015 insertions, 2231 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 31031ab..efbab1a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -95,9 +95,9 @@ jobs:
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"
- xcopy /E /I "${{ github.workspace }}\fonts" "${{ runner.workspace }}\build\fonts"
+ xcopy /E /I "${{ github.workspace }}\res\css" "${{ runner.workspace }}\build\css"
+ xcopy /E /I "${{ github.workspace }}\res\res" "${{ runner.workspace }}\build\res"
+ xcopy /E /I "${{ github.workspace }}\res\fonts" "${{ runner.workspace }}\build\fonts"
mkdir "${{ runner.workspace }}\build\share"
xcopy /E /I "${{ github.workspace }}\ci\gtk-for-windows\gtk-nsis-pack\share\glib-2.0" "${{ runner.workspace }}\build\share\glib-2.0"
copy "${{ github.workspace }}\ci\vcpkg\installed\x64-windows\tools\glib\gspawn-win64-helper.exe" "${{ runner.workspace }}\build\gspawn-win64-helper.exe"
@@ -137,8 +137,8 @@ jobs:
run: |
mkdir "${{ runner.workspace }}/artifactdir"
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
- cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
- cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
+ cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
+ cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
- name: Upload build
uses: actions/upload-artifact@v2
@@ -190,8 +190,8 @@ jobs:
run: |
mkdir "${{ runner.workspace }}/artifactdir"
cp "${{runner.workspace}}/build/abaddon" "${{ runner.workspace }}/artifactdir/abaddon"
- cp -r "${{ github.workspace }}/css" "${{ runner.workspace }}/artifactdir/css"
- cp -r "${{ github.workspace }}/res" "${{ runner.workspace }}/artifactdir/res"
+ cp -r "${{ github.workspace }}/res/css" "${{ runner.workspace }}/artifactdir/css"
+ cp -r "${{ github.workspace }}/res/res" "${{ runner.workspace }}/artifactdir/res"
- name: Upload build
uses: actions/upload-artifact@v2
diff --git a/.gitmodules b/.gitmodules
index bcbf397..65a1996 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,15 +1,12 @@
[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/machinezone/ixwebsocket
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 638be3e..fa56d6c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,17 +18,10 @@ 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(thirdparty/IXWebSocket)
+ add_subdirectory(subprojects/ixwebsocket)
include_directories(IXWEBSOCKET_INCLUDE_DIRS)
endif()
-add_compile_definitions(SI_NO_CONVERSION) # only CSimpleIniA is used
-find_package(simpleini QUIET)
-if (NOT simpleini_FOUND)
- message("simpleini was not found and will be included as a submodule")
- include_directories(thirdparty/simpleini)
-endif()
-
if(MINGW OR WIN32)
link_libraries(ws2_32)
endif()
@@ -41,28 +34,16 @@ if(WIN32)
link_libraries(${Fontconfig_LIBRARIES})
endif()
-configure_file(${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
+configure_file(${PROJECT_SOURCE_DIR}/src/config.h.in ${PROJECT_BINARY_DIR}/config.h)
-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"
+file(GLOB_RECURSE ABADDON_SOURCES
+ "src/*.h"
+ "src/*.hpp"
+ "src/*.cpp"
)
add_executable(abaddon ${ABADDON_SOURCES})
-target_include_directories(abaddon PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+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})
diff --git a/README.md b/README.md
index 7a890a8..8588abd 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ Alternative Discord client made in C++ with GTK
<img src="/.readme/s3.png">
-[😎Discord Server](https://discord.gg/wkCU3vuzG5)
+<a href="https://discord.gg/wkCU3vuzG5"><img src="https://discord.com/api/guilds/858156817711890443/widget.png?style=shield"></a>
Current features:
* Not Electron
@@ -32,7 +32,7 @@ Current features:
### Building manually (recommended if not on Windows):
#### 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`
+2. `vcpkg install gtkmm:x64-windows nlohmann-json:x64-windows ixwebsocket:x64-windows zlib:x64-windows sqlite3:x64-windows openssl:x64-windows curl:x64-windows`
3. `mkdir build && cd build`
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
@@ -75,7 +75,6 @@ On Linux, `css` and `res` can also be loaded from `~/.local/share/abaddon` or `/
* [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)
### TODO:
@@ -178,18 +177,24 @@ Used in profile popup:
### 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
#### http
* user_agent (string) - sets the user-agent to use in HTTP requests to the Discord API (not including media/images)
-* concurrent (int, default 10) - how many images can be concurrently retrieved
+* concurrent (int, default 20) - how many images can be concurrently retrieved
#### gui
* member_list_discriminator (true or false, default true) - show user discriminators in the member list
@@ -199,8 +204,6 @@ For example, memory_db would be set by adding `memory_db = true` under the line
* animations (true or false, default true) - use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used
* animated_guild_hover_only (true or false, default true) - only animate guild icons when the guild is being hovered over
* owner_crown (true or false, default true) - show a crown next to the owner
-* gateway (string) - override url for Discord gateway. must be json format and use zlib stream compression
-* api_base (string) - override base url for Discord API
#### style
* linkcolor (string) - color to use for links in messages
diff --git a/cmake/Findsimpleini.cmake b/cmake/Findsimpleini.cmake
deleted file mode 100644
index fa6598a..0000000
--- a/cmake/Findsimpleini.cmake
+++ /dev/null
@@ -1,15 +0,0 @@
-set(simpleini_LIBRARY_NAME simpleini)
-
-find_path(simpleini_INCLUDE_DIR
- NAMES SimpleIni.h
- HINTS /usr/include
- /usr/local/include
- /opt/local/include
- PATH_SUFFIXES ${simpleini_LIBRARY_NAME})
-
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(simpleini
- REQUIRED_VARS
- simpleini_INCLUDE_DIR)
-
-mark_as_advanced(simpleini_INCLUDE_DIR)
diff --git a/discord/store.cpp b/discord/store.cpp
deleted file mode 100644
index bc73f67..0000000
--- a/discord/store.cpp
+++ /dev/null
@@ -1,1507 +0,0 @@
-#include "store.hpp"
-
-using namespace std::literals::string_literals;
-
-// hopefully the casting between signed and unsigned int64 doesnt cause issues
-
-Store::Store(bool mem_store) {
- if (mem_store) {
- m_db_path = ":memory:";
- m_db_err = sqlite3_open(":memory:", &m_db);
- } else {
- m_db_path = std::filesystem::temp_directory_path() / "abaddon-store.db";
- m_db_err = sqlite3_open(m_db_path.string().c_str(), &m_db);
- }
-
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error opening database: %s\n", sqlite3_errstr(m_db_err));
- return;
- }
-
- // clang-format off
- m_db_err = sqlite3_exec(m_db, R"(
- PRAGMA writable_schema = 1;
- DELETE FROM sqlite_master;
- PRAGMA writable_schema = 0;
- VACUUM;
- PRAGMA integrity_check;
- )", nullptr, nullptr, nullptr);
- // clang-format on
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to clear database: %s\n", sqlite3_errstr(m_db_err));
- return;
- }
-
- m_db_err = sqlite3_exec(m_db, "PRAGMA journal_mode = WAL", nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "enabling write-ahead-log failed: %s\n", sqlite3_errstr(m_db_err));
- return;
- }
-
- m_db_err = sqlite3_exec(m_db, "PRAGMA synchronous = NORMAL", nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "setting synchronous failed: %s\n", sqlite3_errstr(m_db_err));
- return;
- }
-
- CreateTables();
- CreateStatements();
-}
-
-Store::~Store() {
- Cleanup();
-
- m_db_err = sqlite3_close(m_db);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error closing database: %s\n", sqlite3_errstr(m_db_err));
- return;
- }
-
- if (m_db_path != ":memory:") {
- std::error_code ec;
- std::filesystem::remove(m_db_path, ec);
- }
-}
-
-bool Store::IsValid() const {
- return m_db_err == SQLITE_OK;
-}
-
-void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) {
- Bind(m_set_ban_stmt, 1, guild_id);
- Bind(m_set_ban_stmt, 2, user_id);
- Bind(m_set_ban_stmt, 3, ban.Reason);
-
- if (!RunInsert(m_set_ban_stmt))
- fprintf(stderr, "ban insert failed: %s\n", sqlite3_errstr(m_db_err));
-}
-
-void Store::SetChannel(Snowflake id, const ChannelData &chan) {
- Bind(m_set_chan_stmt, 1, id);
- Bind(m_set_chan_stmt, 2, static_cast<int>(chan.Type));
- Bind(m_set_chan_stmt, 3, chan.GuildID);
- Bind(m_set_chan_stmt, 4, chan.Position);
- Bind(m_set_chan_stmt, 5, nullptr); // unused
- Bind(m_set_chan_stmt, 6, chan.Name);
- Bind(m_set_chan_stmt, 7, chan.Topic);
- Bind(m_set_chan_stmt, 8, chan.IsNSFW);
- Bind(m_set_chan_stmt, 9, chan.LastMessageID);
- Bind(m_set_chan_stmt, 10, chan.Bitrate);
- Bind(m_set_chan_stmt, 11, chan.UserLimit);
- Bind(m_set_chan_stmt, 12, chan.RateLimitPerUser);
- if (chan.Recipients.has_value()) {
- std::vector<Snowflake> ids;
- for (const auto &u : *chan.Recipients)
- ids.push_back(u.ID);
- Bind(m_set_chan_stmt, 13, nlohmann::json(ids).dump());
- } else if (chan.RecipientIDs.has_value()) {
- Bind(m_set_chan_stmt, 13, nlohmann::json(*chan.RecipientIDs).dump());
- } else {
- Bind(m_set_chan_stmt, 13, nullptr);
- }
- Bind(m_set_chan_stmt, 14, chan.Icon);
- Bind(m_set_chan_stmt, 15, chan.OwnerID);
- Bind(m_set_chan_stmt, 16, chan.ApplicationID);
- Bind(m_set_chan_stmt, 17, chan.ParentID);
- Bind(m_set_chan_stmt, 18, chan.LastPinTimestamp);
-
- if (chan.ThreadMetadata.has_value()) {
- Bind(m_set_chan_stmt, 19, chan.ThreadMetadata->IsArchived);
- Bind(m_set_chan_stmt, 20, chan.ThreadMetadata->AutoArchiveDuration);
- Bind(m_set_chan_stmt, 21, chan.ThreadMetadata->ArchiveTimestamp);
- } else {
- Bind(m_set_chan_stmt, 19, nullptr);
- Bind(m_set_chan_stmt, 20, nullptr);
- Bind(m_set_chan_stmt, 21, nullptr);
- }
-
- if (!RunInsert(m_set_chan_stmt))
- fprintf(stderr, "channel insert failed: %s\n", sqlite3_errstr(m_db_err));
-
- m_channels.insert(id);
-}
-
-void Store::SetEmoji(Snowflake id, const EmojiData &emoji) {
- Bind(m_set_emote_stmt, 1, id);
- Bind(m_set_emote_stmt, 2, emoji.Name);
-
- if (emoji.Roles.has_value())
- Bind(m_set_emote_stmt, 3, nlohmann::json(*emoji.Roles).dump());
- else
- Bind(m_set_emote_stmt, 3, nullptr);
-
- if (emoji.Creator.has_value())
- Bind(m_set_emote_stmt, 4, emoji.Creator->ID);
- else
- Bind(m_set_emote_stmt, 4, nullptr);
-
- Bind(m_set_emote_stmt, 5, emoji.NeedsColons);
- Bind(m_set_emote_stmt, 6, emoji.IsManaged);
- Bind(m_set_emote_stmt, 7, emoji.IsAnimated);
- Bind(m_set_emote_stmt, 8, emoji.IsAvailable);
-
- if (!RunInsert(m_set_emote_stmt))
- fprintf(stderr, "emoji insert failed: %s\n", sqlite3_errstr(m_db_err));
-}
-
-void Store::SetGuild(Snowflake id, const GuildData &guild) {
- Bind(m_set_guild_stmt, 1, id);
- Bind(m_set_guild_stmt, 2, guild.Name);
- Bind(m_set_guild_stmt, 3, guild.Icon);
- Bind(m_set_guild_stmt, 4, guild.Splash);
- Bind(m_set_guild_stmt, 5, guild.IsOwner);
- Bind(m_set_guild_stmt, 6, guild.OwnerID);
- Bind(m_set_guild_stmt, 7, guild.PermissionsNew);
- Bind(m_set_guild_stmt, 8, guild.VoiceRegion);
- Bind(m_set_guild_stmt, 9, guild.AFKChannelID);
- Bind(m_set_guild_stmt, 10, guild.AFKTimeout);
- Bind(m_set_guild_stmt, 11, guild.VerificationLevel);
- Bind(m_set_guild_stmt, 12, guild.DefaultMessageNotifications);
- std::vector<Snowflake> snowflakes;
- if (guild.Roles.has_value()) {
- for (const auto &x : *guild.Roles) snowflakes.push_back(x.ID);
- Bind(m_set_guild_stmt, 13, nlohmann::json(snowflakes).dump());
- } else {
- Bind(m_set_guild_stmt, 13, "[]"s);
- }
- snowflakes.clear();
- if (guild.Emojis.has_value()) {
- for (const auto &x : *guild.Emojis) snowflakes.push_back(x.ID);
- Bind(m_set_guild_stmt, 14, nlohmann::json(snowflakes).dump());
- } else {
- Bind(m_set_guild_stmt, 14, "[]"s);
- }
- if (guild.Features.has_value())
- Bind(m_set_guild_stmt, 15, nlohmann::json(*guild.Features).dump());
- else
- Bind(m_set_guild_stmt, 15, "[]"s);
- Bind(m_set_guild_stmt, 16, guild.MFALevel);
- Bind(m_set_guild_stmt, 17, guild.ApplicationID);
- Bind(m_set_guild_stmt, 18, guild.IsWidgetEnabled);
- Bind(m_set_guild_stmt, 19, guild.WidgetChannelID);
- Bind(m_set_guild_stmt, 20, guild.SystemChannelFlags);
- Bind(m_set_guild_stmt, 21, guild.RulesChannelID);
- Bind(m_set_guild_stmt, 22, guild.JoinedAt);
- Bind(m_set_guild_stmt, 23, guild.IsLarge);
- Bind(m_set_guild_stmt, 24, guild.IsUnavailable);
- Bind(m_set_guild_stmt, 25, guild.MemberCount);
- if (guild.Channels.has_value()) {
- snowflakes.clear();
- for (const auto &x : *guild.Channels) snowflakes.push_back(x.ID);
- Bind(m_set_guild_stmt, 26, nlohmann::json(snowflakes).dump());
- } else
- Bind(m_set_guild_stmt, 26, "[]"s);
- Bind(m_set_guild_stmt, 27, guild.MaxPresences);
- Bind(m_set_guild_stmt, 28, guild.MaxMembers);
- Bind(m_set_guild_stmt, 29, guild.VanityURL);
- Bind(m_set_guild_stmt, 30, guild.Description);
- Bind(m_set_guild_stmt, 31, guild.BannerHash);
- Bind(m_set_guild_stmt, 32, guild.PremiumTier);
- Bind(m_set_guild_stmt, 33, guild.PremiumSubscriptionCount);
- Bind(m_set_guild_stmt, 34, guild.PreferredLocale);
- Bind(m_set_guild_stmt, 35, guild.PublicUpdatesChannelID);
- Bind(m_set_guild_stmt, 36, guild.MaxVideoChannelUsers);
- Bind(m_set_guild_stmt, 37, guild.ApproximateMemberCount);
- Bind(m_set_guild_stmt, 38, guild.ApproximatePresenceCount);
- Bind(m_set_guild_stmt, 39, guild.IsLazy);
- if (guild.Threads.has_value()) {
- snowflakes.clear();
- for (const auto &x : *guild.Threads) snowflakes.push_back(x.ID);
- Bind(m_set_guild_stmt, 40, nlohmann::json(snowflakes).dump());
- } else
- Bind(m_set_guild_stmt, 40, "[]"s);
-
- if (!RunInsert(m_set_guild_stmt))
- fprintf(stderr, "guild insert failed: %s\n", sqlite3_errstr(m_db_err));
-
- m_guilds.insert(id);
-}
-
-void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data) {
- Bind(m_set_member_stmt, 1, user_id);
- Bind(m_set_member_stmt, 2, guild_id);
- Bind(m_set_member_stmt, 3, data.Nickname);
- Bind(m_set_member_stmt, 4, nlohmann::json(data.Roles).dump());
- Bind(m_set_member_stmt, 5, data.JoinedAt);
- Bind(m_set_member_stmt, 6, data.PremiumSince);
- Bind(m_set_member_stmt, 7, data.IsDeafened);
- Bind(m_set_member_stmt, 8, data.IsMuted);
- Bind(m_set_member_stmt, 9, data.Avatar);
- Bind(m_set_member_stmt, 10, data.IsPending);
-
- if (!RunInsert(m_set_member_stmt))
- fprintf(stderr, "member insert failed: %s\n", sqlite3_errstr(m_db_err));
-}
-
-void Store::SetMessage(Snowflake id, const Message &message) {
- Bind(m_set_msg_stmt, 1, id);
- Bind(m_set_msg_stmt, 2, message.ChannelID);
- Bind(m_set_msg_stmt, 3, message.GuildID);
- Bind(m_set_msg_stmt, 4, message.Author.ID);
- Bind(m_set_msg_stmt, 5, message.Content);
- Bind(m_set_msg_stmt, 6, message.Timestamp);
- Bind(m_set_msg_stmt, 7, message.EditedTimestamp);
- Bind(m_set_msg_stmt, 8, message.IsTTS);
- Bind(m_set_msg_stmt, 9, message.DoesMentionEveryone);
- Bind(m_set_msg_stmt, 10, nlohmann::json(message.Mentions).dump());
- Bind(m_set_msg_stmt, 11, nlohmann::json(message.Attachments).dump());
- Bind(m_set_msg_stmt, 12, nlohmann::json(message.Embeds).dump());
- Bind(m_set_msg_stmt, 13, message.IsPinned);
- Bind(m_set_msg_stmt, 14, message.WebhookID);
- Bind(m_set_msg_stmt, 15, static_cast<uint64_t>(message.Type));
-
- if (message.Application.has_value())
- Bind(m_set_msg_stmt, 16, nlohmann::json(*message.Application).dump());
- else
- Bind(m_set_msg_stmt, 16, nullptr);
-
- if (message.MessageReference.has_value())
- Bind(m_set_msg_stmt, 17, nlohmann::json(*message.MessageReference).dump());
- else
- Bind(m_set_msg_stmt, 17, nullptr);
-
- if (message.Flags.has_value())
- Bind(m_set_msg_stmt, 18, static_cast<uint64_t>(*message.Flags));
- else
- Bind(m_set_msg_stmt, 18, nullptr);
-
- if (message.Stickers.has_value())
- Bind(m_set_msg_stmt, 19, nlohmann::json(*message.Stickers).dump());
- else
- Bind(m_set_msg_stmt, 19, nullptr);
-
- if (message.Reactions.has_value()) {
- std::string tmp = nlohmann::json(*message.Reactions).dump();
- Bind(m_set_msg_stmt, 20, tmp);
- } else
- Bind(m_set_msg_stmt, 20, nullptr);
- Bind(m_set_msg_stmt, 21, message.IsDeleted());
- Bind(m_set_msg_stmt, 22, message.IsEdited());
- Bind(m_set_msg_stmt, 23, message.IsPending);
- Bind(m_set_msg_stmt, 24, message.Nonce); // sorry
-
- if (message.StickerItems.has_value()) {
- std::string tmp = nlohmann::json(*message.StickerItems).dump();
- Bind(m_set_msg_stmt, 25, tmp);
- } else
- Bind(m_set_msg_stmt, 25, nullptr);
-
- if (!RunInsert(m_set_msg_stmt))
- fprintf(stderr, "message insert failed: %s\n", sqlite3_errstr(m_db_err));
-
- if (message.Interaction.has_value())
- SetMessageInteractionPair(id, *message.Interaction);
-}
-
-void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) {
- Bind(m_set_perm_stmt, 1, perm.ID);
- Bind(m_set_perm_stmt, 2, channel_id);
- Bind(m_set_perm_stmt, 3, static_cast<int>(perm.Type));
- Bind(m_set_perm_stmt, 4, static_cast<uint64_t>(perm.Allow));
- Bind(m_set_perm_stmt, 5, static_cast<uint64_t>(perm.Deny));
-
- if (!RunInsert(m_set_perm_stmt))
- fprintf(stderr, "permission insert failed: %s\n", sqlite3_errstr(m_db_err));
-}
-
-void Store::SetRole(Snowflake id, const RoleData &role) {
- Bind(m_set_role_stmt, 1, id);
- Bind(m_set_role_stmt, 2, role.Name);
- Bind(m_set_role_stmt, 3, role.Color);
- Bind(m_set_role_stmt, 4, role.IsHoisted);
- Bind(m_set_role_stmt, 5, role.Position);
- Bind(m_set_role_stmt, 6, static_cast<uint64_t>(role.Permissions));
- Bind(m_set_role_stmt, 7, role.IsManaged);
- Bind(m_set_role_stmt, 8, role.IsMentionable);
-
- if (!RunInsert(m_set_role_stmt))
- fprintf(stderr, "role insert failed: %s\n", sqlite3_errstr(m_db_err));
-}
-
-void Store::SetUser(Snowflake id, const UserData &user) {
- Bind(m_set_user_stmt, 1, id);
- Bind(m_set_user_stmt, 2, user.Username);
- Bind(m_set_user_stmt, 3, user.Discriminator);
- Bind(m_set_user_stmt, 4, user.Avatar);
- Bind(m_set_user_stmt, 5, user.IsBot);
- Bind(m_set_user_stmt, 6, user.IsSystem);
- Bind(m_set_user_stmt, 7, user.IsMFAEnabled);
- Bind(m_set_user_stmt, 8, user.Locale);
- Bind(m_set_user_stmt, 9, user.IsVerified);
- Bind(m_set_user_stmt, 10, user.Email);
- Bind(m_set_user_stmt, 11, user.Flags);
- Bind(m_set_user_stmt, 12, user.PremiumType);
- Bind(m_set_user_stmt, 13, user.PublicFlags);
-
- if (!RunInsert(m_set_user_stmt)) {
- fprintf(stderr, "user insert failed: %s\n", sqlite3_errstr(m_db_err));
- }
-}
-
-Message Store::GetMessageBound(sqlite3_stmt *stmt) const {
- Message ret;
- Get(stmt, 0, ret.ID);
- Get(stmt, 1, ret.ChannelID);
- Get(stmt, 2, ret.GuildID);
- Get(stmt, 3, ret.Author.ID); // yike
- Get(stmt, 4, ret.Content);
- Get(stmt, 5, ret.Timestamp);
- Get(stmt, 6, ret.EditedTimestamp);
- Get(stmt, 7, ret.IsTTS);
- Get(stmt, 8, ret.DoesMentionEveryone);
- std::string tmps;
- Get(stmt, 9, tmps);
- nlohmann::json::parse(tmps).get_to(ret.Mentions);
- Get(stmt, 10, tmps);
- nlohmann::json::parse(tmps).get_to(ret.Attachments);
- Get(stmt, 11, tmps);
- nlohmann::json::parse(tmps).get_to(ret.Embeds);
- Get(stmt, 12, ret.IsPinned);
- Get(stmt, 13, ret.WebhookID);
- uint64_t tmpi;
- Get(stmt, 14, tmpi);
- ret.Type = static_cast<MessageType>(tmpi);
-
- Get(stmt, 15, tmps);
- if (tmps != "")
- ret.Application = nlohmann::json::parse(tmps).get<MessageApplicationData>();
-
- Get(stmt, 16, tmps);
- if (tmps != "")
- ret.MessageReference = nlohmann::json::parse(tmps).get<MessageReferenceData>();
-
- Get(stmt, 17, tmpi);
- ret.Flags = static_cast<MessageFlags>(tmpi);
-
- Get(stmt, 18, tmps);
- if (tmps != "")
- ret.Stickers = nlohmann::json::parse(tmps).get<std::vector<StickerData>>();
-
- Get(stmt, 19, tmps);
- if (tmps != "")
- ret.Reactions = nlohmann::json::parse(tmps).get<std::vector<ReactionData>>();
-
- bool tmpb = false;
- Get(stmt, 20, tmpb);
- if (tmpb) ret.SetDeleted();
-
- Get(stmt, 21, tmpb);
- if (tmpb) ret.SetEdited();
-
- Get(stmt, 22, ret.IsPending);
- Get(stmt, 23, ret.Nonce);
-
- Get(stmt, 24, tmps);
- if (tmps != "")
- ret.StickerItems = nlohmann::json::parse(tmps).get<std::vector<StickerItem>>();
-
- // interaction data from join
-
- if (!IsNull(stmt, 25)) {
- auto &interaction = ret.Interaction.emplace();
- Get(stmt, 25, interaction.ID);
- Get(stmt, 26, interaction.Name);
- Get(stmt, 27, interaction.Type);
- Get(stmt, 28, interaction.User.ID);
- }
-
- Reset(stmt);
-
- if (ret.MessageReference.has_value() && ret.MessageReference->MessageID.has_value()) {
- auto ref = GetMessage(*ret.MessageReference->MessageID);
- if (ref.has_value())
- ret.ReferencedMessage = std::make_unique<Message>(std::move(*ref));
- else
- ret.ReferencedMessage = nullptr;
- }
-
- return ret;
-}
-
-void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
- Bind(m_set_msg_interaction_stmt, 1, message_id);
- Bind(m_set_msg_interaction_stmt, 2, interaction.ID);
- Bind(m_set_msg_interaction_stmt, 3, interaction.Type);
- Bind(m_set_msg_interaction_stmt, 4, interaction.Name);
- Bind(m_set_msg_interaction_stmt, 5, interaction.User.ID);
-
- if (!RunInsert(m_set_msg_interaction_stmt)) {
- fprintf(stderr, "message interaction insert failed: %s\n", sqlite3_errstr(m_db_err));
- }
-}
-
-std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const {
- Bind(m_get_ban_stmt, 1, guild_id);
- Bind(m_get_ban_stmt, 2, user_id);
- if (!FetchOne(m_get_ban_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching ban: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_ban_stmt);
- return std::nullopt;
- }
-
- BanData ret;
- ret.User.ID = user_id;
- Get(m_get_ban_stmt, 2, ret.Reason);
-
- Reset(m_get_ban_stmt);
- return ret;
-}
-
-std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
- Bind(m_get_bans_stmt, 1, guild_id);
-
- std::vector<BanData> ret;
- while (FetchOne(m_get_bans_stmt)) {
- auto &ban = ret.emplace_back();
- Get(m_get_bans_stmt, 1, ban.User.ID);
- Get(m_get_bans_stmt, 2, ban.Reason);
- }
-
- Reset(m_get_bans_stmt);
-
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching bans: %s\n", sqlite3_errstr(m_db_err));
- return ret;
-}
-
-std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
- auto ids = GetChannelMessageIDs(id);
- std::vector<Message> ret;
- for (auto it = ids.cend() - std::min(ids.size(), num); it != ids.cend(); it++)
- ret.push_back(*GetMessage(*it));
- return ret;
-}
-
-std::vector<Snowflake> Store::GetChannelMessageIDs(Snowflake id) const {
- std::vector<Snowflake> ret;
- Bind(m_get_msg_ids_stmt, 1, id);
-
- while (FetchOne(m_get_msg_ids_stmt)) {
- Snowflake x;
- Get(m_get_msg_ids_stmt, 0, x);
- ret.push_back(x);
- }
-
- Reset(m_get_msg_ids_stmt);
-
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching ids: %s\n", sqlite3_errstr(m_db_err));
- return ret;
-}
-
-std::vector<Message> Store::GetPinnedMessages(Snowflake channel_id) const {
- std::vector<Message> ret;
-
- Bind(m_get_pins_stmt, 1, channel_id);
- while (FetchOne(m_get_pins_stmt)) {
- Snowflake x;
- Get(m_get_pins_stmt, 0, x);
- auto msg = GetMessage(x);
- if (msg.has_value())
- ret.push_back(*msg);
- }
-
- Reset(m_get_pins_stmt);
-
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching pins: %s\n", sqlite3_errstr(m_db_err));
- return ret;
-}
-
-std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
- std::vector<ChannelData> ret;
-
- Bind(m_get_threads_stmt, 1, channel_id);
- while (FetchOne(m_get_threads_stmt)) {
- Snowflake x;
- Get(m_get_threads_stmt, 0, x);
- auto chan = GetChannel(x);
- if (chan.has_value())
- ret.push_back(*chan);
- }
-
- Reset(m_get_threads_stmt);
-
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching threads: %s\n", sqlite3_errstr(m_db_err));
- return ret;
-}
-
-std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
- Bind(m_get_chan_stmt, 1, id);
- if (!FetchOne(m_get_chan_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching channel: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_chan_stmt);
- return std::nullopt;
- }
-
- ChannelData ret;
- ret.ID = id;
- int tmpi;
- Get(m_get_chan_stmt, 1, tmpi);
- ret.Type = static_cast<ChannelType>(tmpi);
- Get(m_get_chan_stmt, 2, ret.GuildID);
- Get(m_get_chan_stmt, 3, ret.Position);
- ret.PermissionOverwrites = std::nullopt;
- Get(m_get_chan_stmt, 5, ret.Name);
- Get(m_get_chan_stmt, 6, ret.Topic);
- Get(m_get_chan_stmt, 7, ret.IsNSFW);
- Get(m_get_chan_stmt, 8, ret.LastMessageID);
- Get(m_get_chan_stmt, 9, ret.Bitrate);
- Get(m_get_chan_stmt, 10, ret.UserLimit);
- Get(m_get_chan_stmt, 11, ret.RateLimitPerUser);
- if (!IsNull(m_get_chan_stmt, 12)) {
- std::string tmps;
- Get(m_get_chan_stmt, 12, tmps);
- ret.RecipientIDs = nlohmann::json::parse(tmps).get<std::vector<Snowflake>>();
- }
- Get(m_get_chan_stmt, 13, ret.Icon);
- Get(m_get_chan_stmt, 14, ret.OwnerID);
- Get(m_get_chan_stmt, 15, ret.ApplicationID);
- Get(m_get_chan_stmt, 16, ret.ParentID);
- Get(m_get_chan_stmt, 17, ret.LastPinTimestamp);
- if (!IsNull(m_get_chan_stmt, 18)) {
- ret.ThreadMetadata.emplace();
- Get(m_get_chan_stmt, 18, ret.ThreadMetadata->IsArchived);
- Get(m_get_chan_stmt, 19, ret.ThreadMetadata->AutoArchiveDuration);
- Get(m_get_chan_stmt, 20, ret.ThreadMetadata->ArchiveTimestamp);
- }
-
- Reset(m_get_chan_stmt);
-
- return ret;
-}
-
-std::optional<EmojiData> Store::GetEmoji(Snowflake id) const {
- Bind(m_get_emote_stmt, 1, id);
- if (!FetchOne(m_get_emote_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching emoji: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_emote_stmt);
- return std::nullopt;
- }
-
- EmojiData ret;
- ret.ID = id;
- Get(m_get_emote_stmt, 1, ret.Name);
-
- if (!IsNull(m_get_emote_stmt, 2)) {
- std::string tmp;
- Get(m_get_emote_stmt, 2, tmp);
- ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>();
- }
-
- if (!IsNull(m_get_emote_stmt, 3)) {
- ret.Creator = std::optional<UserData>(UserData());
- Get(m_get_emote_stmt, 3, ret.Creator->ID);
- }
- Get(m_get_emote_stmt, 4, ret.NeedsColons);
- Get(m_get_emote_stmt, 5, ret.IsManaged);
- Get(m_get_emote_stmt, 6, ret.IsAnimated);
- Get(m_get_emote_stmt, 7, ret.IsAvailable);
-
- Reset(m_get_emote_stmt);
-
- return ret;
-}
-
-std::optional<GuildData> Store::GetGuild(Snowflake id) const {
- Bind(m_get_guild_stmt, 1, id);
- if (!FetchOne(m_get_guild_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching guild: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_guild_stmt);
- return std::nullopt;
- }
-
- GuildData ret;
- ret.ID = id;
- Get(m_get_guild_stmt, 1, ret.Name);
- Get(m_get_guild_stmt, 2, ret.Icon);
- Get(m_get_guild_stmt, 3, ret.Splash);
- Get(m_get_guild_stmt, 4, ret.IsOwner);
- Get(m_get_guild_stmt, 5, ret.OwnerID);
- Get(m_get_guild_stmt, 6, ret.PermissionsNew);
- Get(m_get_guild_stmt, 7, ret.VoiceRegion);
- Get(m_get_guild_stmt, 8, ret.AFKChannelID);
- Get(m_get_guild_stmt, 9, ret.AFKTimeout);
- Get(m_get_guild_stmt, 10, ret.VerificationLevel);
- Get(m_get_guild_stmt, 11, ret.DefaultMessageNotifications);
- std::string tmp;
- Get(m_get_guild_stmt, 12, tmp);
- ret.Roles.emplace();
- for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
- ret.Roles->emplace_back().ID = id;
- Get(m_get_guild_stmt, 13, tmp);
- ret.Emojis.emplace();
- for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
- ret.Emojis->emplace_back().ID = id;
- Get(m_get_guild_stmt, 14, tmp);
- ret.Features = nlohmann::json::parse(tmp).get<std::unordered_set<std::string>>();
- Get(m_get_guild_stmt, 15, ret.MFALevel);
- Get(m_get_guild_stmt, 16, ret.ApplicationID);
- Get(m_get_guild_stmt, 17, ret.IsWidgetEnabled);
- Get(m_get_guild_stmt, 18, ret.WidgetChannelID);
- Get(m_get_guild_stmt, 19, ret.SystemChannelFlags);
- Get(m_get_guild_stmt, 20, ret.RulesChannelID);
- Get(m_get_guild_stmt, 21, ret.JoinedAt);
- Get(m_get_guild_stmt, 22, ret.IsLarge);
- Get(m_get_guild_stmt, 23, ret.IsUnavailable);
- Get(m_get_guild_stmt, 24, ret.MemberCount);
- Get(m_get_guild_stmt, 25, tmp);
- ret.Channels.emplace();
- for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
- ret.Channels->emplace_back().ID = id;
- Get(m_get_guild_stmt, 26, ret.MaxPresences);
- Get(m_get_guild_stmt, 27, ret.MaxMembers);
- Get(m_get_guild_stmt, 28, ret.VanityURL);
- Get(m_get_guild_stmt, 29, ret.Description);
- Get(m_get_guild_stmt, 30, ret.BannerHash);
- Get(m_get_guild_stmt, 31, ret.PremiumTier);
- Get(m_get_guild_stmt, 32, ret.PremiumSubscriptionCount);
- Get(m_get_guild_stmt, 33, ret.PreferredLocale);
- Get(m_get_guild_stmt, 34, ret.PublicUpdatesChannelID);
- Get(m_get_guild_stmt, 35, ret.MaxVideoChannelUsers);
- Get(m_get_guild_stmt, 36, ret.ApproximateMemberCount);
- Get(m_get_guild_stmt, 37, ret.ApproximatePresenceCount);
- Get(m_get_guild_stmt, 38, ret.IsLazy);
- Get(m_get_guild_stmt, 39, tmp);
- ret.Threads.emplace();
- for (const auto &id : nlohmann::json::parse(tmp).get<std::vector<Snowflake>>())
- ret.Threads->emplace_back().ID = id;
-
- Reset(m_get_guild_stmt);
-
- return ret;
-}
-
-std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake user_id) const {
- Bind(m_get_member_stmt, 1, user_id);
- Bind(m_get_member_stmt, 2, guild_id);
- if (!FetchOne(m_get_member_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching member: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_member_stmt);
- return std::nullopt;
- }
-
- GuildMember ret;
- ret.User.emplace().ID = user_id;
- Get(m_get_member_stmt, 2, ret.Nickname);
- std::string tmp;
- Get(m_get_member_stmt, 3, tmp);
- ret.Roles = nlohmann::json::parse(tmp).get<std::vector<Snowflake>>();
- Get(m_get_member_stmt, 4, ret.JoinedAt);
- Get(m_get_member_stmt, 5, ret.PremiumSince);
- Get(m_get_member_stmt, 6, ret.IsDeafened);
- Get(m_get_member_stmt, 7, ret.IsMuted);
- Get(m_get_member_stmt, 8, ret.Avatar);
- Get(m_get_member_stmt, 9, ret.IsPending);
-
- Reset(m_get_member_stmt);
-
- return ret;
-}
-
-std::optional<Message> Store::GetMessage(Snowflake id) const {
- Bind(m_get_msg_stmt, 1, id);
- if (!FetchOne(m_get_msg_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching message: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_msg_stmt);
- return std::nullopt;
- }
-
- auto ret = GetMessageBound(m_get_msg_stmt);
-
- return std::optional<Message>(std::move(ret));
-}
-
-std::optional<PermissionOverwrite> Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const {
- Bind(m_get_perm_stmt, 1, id);
- Bind(m_get_perm_stmt, 2, channel_id);
- if (!FetchOne(m_get_perm_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching permission: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_perm_stmt);
- return std::nullopt;
- }
-
- PermissionOverwrite ret;
- ret.ID = id;
- uint64_t tmp;
- Get(m_get_perm_stmt, 2, tmp);
- ret.Type = static_cast<PermissionOverwrite::OverwriteType>(tmp);
- Get(m_get_perm_stmt, 3, tmp);
- ret.Allow = static_cast<Permission>(tmp);
- Get(m_get_perm_stmt, 4, tmp);
- ret.Deny = static_cast<Permission>(tmp);
-
- Reset(m_get_perm_stmt);
-
- return ret;
-}
-
-std::optional<RoleData> Store::GetRole(Snowflake id) const {
- Bind(m_get_role_stmt, 1, id);
- if (!FetchOne(m_get_role_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching role: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_role_stmt);
- return std::nullopt;
- }
-
- RoleData ret;
- ret.ID = id;
- Get(m_get_role_stmt, 1, ret.Name);
- Get(m_get_role_stmt, 2, ret.Color);
- Get(m_get_role_stmt, 3, ret.IsHoisted);
- Get(m_get_role_stmt, 4, ret.Position);
- uint64_t tmp;
- Get(m_get_role_stmt, 5, tmp);
- ret.Permissions = static_cast<Permission>(tmp);
- Get(m_get_role_stmt, 6, ret.IsManaged);
- Get(m_get_role_stmt, 7, ret.IsMentionable);
-
- Reset(m_get_role_stmt);
-
- return ret;
-}
-
-std::optional<UserData> Store::GetUser(Snowflake id) const {
- Bind(m_get_user_stmt, 1, id);
- if (!FetchOne(m_get_user_stmt)) {
- if (m_db_err != SQLITE_DONE)
- fprintf(stderr, "error while fetching user info: %s\n", sqlite3_errstr(m_db_err));
- Reset(m_get_user_stmt);
- return std::nullopt;
- }
-
- UserData ret;
- Get(m_get_user_stmt, 0, ret.ID);
- Get(m_get_user_stmt, 1, ret.Username);
- Get(m_get_user_stmt, 2, ret.Discriminator);
- Get(m_get_user_stmt, 3, ret.Avatar);
- Get(m_get_user_stmt, 4, ret.IsBot);
- Get(m_get_user_stmt, 5, ret.IsSystem);
- Get(m_get_user_stmt, 6, ret.IsMFAEnabled);
- Get(m_get_user_stmt, 7, ret.Locale);
- Get(m_get_user_stmt, 8, ret.IsVerified);
- Get(m_get_user_stmt, 9, ret.Email);
- Get(m_get_user_stmt, 10, ret.Flags);
- Get(m_get_user_stmt, 11, ret.PremiumType);
- Get(m_get_user_stmt, 12, ret.PublicFlags);
-
- Reset(m_get_user_stmt);
-
- return ret;
-}
-
-void Store::ClearGuild(Snowflake id) {
- m_guilds.erase(id);
-}
-
-void Store::ClearChannel(Snowflake id) {
- m_channels.erase(id);
- Bind(m_clear_chan_stmt, 1, id);
-
- if ((m_db_err = sqlite3_step(m_clear_chan_stmt)) != SQLITE_DONE)
- printf("clearing channel failed: %s\n", sqlite3_errstr(m_db_err));
-
- Reset(m_clear_chan_stmt);
-}
-
-void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
- Bind(m_clear_ban_stmt, 1, guild_id);
- Bind(m_clear_ban_stmt, 2, user_id);
-
- if ((m_db_err = sqlite3_step(m_clear_ban_stmt)) != SQLITE_DONE)
- printf("clearing ban failed: %s\n", sqlite3_errstr(m_db_err));
-
- Reset(m_clear_ban_stmt);
-}
-
-const std::unordered_set<Snowflake> &Store::GetChannels() const {
- return m_channels;
-}
-
-const std::unordered_set<Snowflake> &Store::GetGuilds() const {
- return m_guilds;
-}
-void Store::ClearAll() {
- m_channels.clear();
- m_guilds.clear();
-}
-
-void Store::BeginTransaction() {
- m_db_err = sqlite3_exec(m_db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr);
-}
-
-void Store::EndTransaction() {
- m_db_err = sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
-}
-
-bool Store::CreateTables() {
- const char *create_users = R"(
- CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY,
- username TEXT NOT NULL,
- discriminator TEXT NOT NULL,
- avatar TEXT,
- bot BOOL,
- system BOOL,
- mfa BOOL,
- locale TEXT,
- verified BOOl,
- email TEXT,
- flags INTEGER,
- premium INTEGER,
- pubflags INTEGER
- )
- )";
-
- const char *create_permissions = R"(
- CREATE TABLE IF NOT EXISTS permissions (
- id INTEGER NOT NULL,
- channel_id INTEGER NOT NULL,
- type INTEGER NOT NULL,
- allow INTEGER NOT NULL,
- deny INTEGER NOT NULL,
- PRIMARY KEY(id, channel_id)
- )
- )";
-
- const char *create_messages = R"(
- CREATE TABLE IF NOT EXISTS messages (
- id INTEGER PRIMARY KEY,
- channel_id INTEGER NOT NULL,
- guild_id INTEGER,
- author_id INTEGER NOT NULL,
- content TEXT NOT NULL,
- timestamp TEXT NOT NULL,
- edited_timestamp TEXT,
- tts BOOL NOT NULL,
- everyone BOOL NOT NULL,
- mentions TEXT NOT NULL, /* json */
- attachments TEXT NOT NULL, /* json */
- embeds TEXT NOT NULL, /* json */
- pinned BOOL,
- webhook_id INTEGER,
- type INTEGER,
- application TEXT, /* json */
- reference TEXT, /* json */
- flags INTEGER,
- stickers TEXT, /* json */
- reactions TEXT, /* json */
- deleted BOOL, /* extra */
- edited BOOL, /* extra */
- pending BOOL, /* extra */
- nonce TEXT,
- sticker_items TEXT /* json */
- )
- )";
-
- const char *create_roles = R"(
- CREATE TABLE IF NOT EXISTS roles (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- color INTEGER NOT NULL,
- hoisted BOOL NOT NULL,
- position INTEGER NOT NULL,
- permissions INTEGER NOT NULL,
- managed BOOL NOT NULL,
- mentionable BOOL NOT NULL
- )
- )";
-
- const char *create_emojis = R"(
- CREATE TABLE IF NOT EXISTS emojis (
- id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
- name TEXT NOT NULL, /*same as id*/
- roles TEXT, /* json */
- creator_id INTEGER,
- colons BOOL,
- managed BOOL,
- animated BOOL,
- available BOOL
- )
- )";
-
- const char *create_members = R"(
- CREATE TABLE IF NOT EXISTS members (
- user_id INTEGER NOT NULL,
- guild_id INTEGER NOT NULL,
- nickname TEXT,
- roles TEXT NOT NULL, /* json */
- joined_at TEXT NOT NULL,
- premium_since TEXT,
- deaf BOOL NOT NULL,
- mute BOOL NOT NULL,
- avatar TEXT,
- pending BOOL,
- PRIMARY KEY(user_id, guild_id)
- )
- )";
-
- const char *create_guilds = R"(
- CREATE TABLE IF NOT EXISTS guilds (
- id INTEGER PRIMARY KEY,
- name TEXT NOT NULL,
- icon TEXT NOT NULL,
- splash TEXT,
- owner BOOL,
- owner_id INTEGER NOT NULL,
- permissions INTEGER, /* new */
- voice_region TEXT,
- afk_id INTEGER,
- afk_timeout INTEGER NOT NULL,
- verification INTEGER NOT NULL,
- notifications INTEGER NOT NULL,
- roles TEXT NOT NULL, /* json */
- emojis TEXT NOT NULL, /* json */
- features TEXT NOT NULL, /* json */
- mfa INTEGER NOT NULL,
- application INTEGER,
- widget BOOL,
- widget_channel INTEGER,
- system_flags INTEGER NOT NULL,
- rules_channel INTEGER,
- joined_at TEXT,
- large BOOL,
- unavailable BOOL,
- member_count INTEGER,
- channels TEXT NOT NULL, /* json */
- max_presences INTEGER,
- max_members INTEGER,
- vanity TEXT,
- description TEXT,
- banner_hash TEXT,
- premium_tier INTEGER NOT NULL,
- premium_count INTEGER,
- locale TEXT NOT NULL,
- public_updates_id INTEGER,
- max_video_users INTEGER,
- approx_members INTEGER,
- approx_presences INTEGER,
- lazy BOOL,
- threads TEXT NOT NULL /* json */
- )
- )";
-
- const char *create_channels = R"(
- CREATE TABLE IF NOT EXISTS channels (
- id INTEGER PRIMARY KEY,
- type INTEGER NOT NULL,
- guild_id INTEGER,
- position INTEGER,
- overwrites TEXT, /* json */
- name TEXT,
- topic TEXT,
- is_nsfw BOOL,
- last_message_id INTEGER,
- bitrate INTEGER,
- user_limit INTEGER,
- rate_limit INTEGER,
- recipients TEXT, /* json */
- icon TEXT,
- owner_id INTEGER,
- application_id INTEGER,
- parent_id INTEGER,
- last_pin_timestamp TEXT,
- archived BOOL, /* threads */
- auto_archive INTEGER, /* threads */
- archived_ts TEXT /* threads */
- )
- )";
-
- const char *create_bans = R"(
- CREATE TABLE IF NOT EXISTS bans (
- guild_id INTEGER NOT NULL,
- user_id INTEGER NOT NULL,
- reason TEXT,
- PRIMARY KEY(user_id, guild_id)
- )
- )";
-
- const char *create_interactions = R"(
- CREATE TABLE IF NOT EXISTS message_interactions (
- message_id INTEGER NOT NULL,
- interaction_id INTEGER NOT NULL,
- type INTEGER NOT NULL,
- name STRING NOT NULL,
- user_id INTEGER NOT NULL,
- PRIMARY KEY(message_id)
- )
- )";
-
- m_db_err = sqlite3_exec(m_db, create_users, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create user table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_permissions, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create permissions table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_messages, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create messages table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_roles, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create roles table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_emojis, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "faile to create emojis table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_members, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create members table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_guilds, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create guilds table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_channels, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create channels table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_bans, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create bans table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_exec(m_db, create_interactions, nullptr, nullptr, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to create message interactions table: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- return true;
-}
-
-bool Store::CreateStatements() {
- const char *set_user = R"(
- REPLACE INTO users VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_user = R"(
- SELECT * FROM users WHERE id = ?
- )";
-
- const char *set_perm = R"(
- REPLACE INTO permissions VALUES (
- ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_perm = R"(
- SELECT * FROM permissions WHERE id = ? AND channel_id = ?
- )";
-
- const char *set_msg = R"(
- REPLACE INTO messages VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_msg = R"(
- SELECT messages.*,
- message_interactions.interaction_id as interaction_id,
- message_interactions.name as interaction_name,
- message_interactions.type as interaction_type,
- message_interactions.user_id as interaction_user_id
- FROM messages
- LEFT OUTER JOIN
- message_interactions
- ON messages.id = message_interactions.message_id
- WHERE id = ?
- )";
-
- const char *set_role = R"(
- REPLACE INTO roles VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_role = R"(
- SELECT * FROM roles WHERE id = ?
- )";
-
- const char *set_emoji = R"(
- REPLACE INTO emojis VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_emoji = R"(
- SELECT * FROM emojis WHERE id = ?
- )";
-
- const char *set_member = R"(
- REPLACE INTO members VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_member = R"(
- SELECT * FROM members WHERE user_id = ? AND guild_id = ?
- )";
-
- const char *set_guild = R"(
- REPLACE INTO guilds VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_guild = R"(
- SELECT * FROM guilds WHERE id = ?
- )";
-
- const char *set_chan = R"(
- REPLACE INTO channels VALUES (
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_chan = R"(
- SELECT * FROM channels WHERE id = ?
- )";
-
- const char *set_ban = R"(
- REPLACE INTO bans VALUES (
- ?, ?, ?
- )
- )";
-
- const char *get_ban = R"(
- SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
- )";
-
- const char *clear_ban = R"(
- DELETE FROM bans WHERE guild_id = ? AND user_id = ?
- )";
-
- const char *get_bans = R"(
- SELECT * FROM bans WHERE guild_id = ?
- )";
-
- const char *set_interaction = R"(
- REPLACE INTO message_interactions VALUES (
- ?, ?, ?, ?, ?
- )
- )";
-
- const char *get_last_msgs = R"(
- SELECT * FROM (
- SELECT * FROM messages
- WHERE channel_id = ?
- ORDER BY id DESC
- LIMIT ?
- ) T1 ORDER BY id ASC
- )";
-
- const char *get_msg_ids = R"(
- SELECT id FROM messages WHERE channel_id = ? AND pending = 0 ORDER BY id ASC
- )";
-
- const char *get_pins = R"(
- SELECT id FROM messages WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
- )";
-
- const char *get_threads = R"(
- SELECT id FROM channels WHERE parent_id = ? AND (type = 10 OR type = 11 OR type = 12) AND archived = FALSE
- )";
-
- const char *clear_chan = R"(
- DELETE FROM channels WHERE id = ?
- )";
-
- m_db_err = sqlite3_prepare_v2(m_db, set_user, -1, &m_set_user_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set user statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_user, -1, &m_get_user_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get user statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_perm, -1, &m_set_perm_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set permission statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_perm, -1, &m_get_perm_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get permission statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_msg, -1, &m_set_msg_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set message statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_msg, -1, &m_get_msg_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get message statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_role, -1, &m_set_role_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set role statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_role, -1, &m_get_role_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get role statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_emoji, -1, &m_set_emote_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set emoji statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_emoji, -1, &m_get_emote_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get emoji statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_member, -1, &m_set_member_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set member statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_member, -1, &m_get_member_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get member statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_guild, -1, &m_set_guild_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set guild statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_guild, -1, &m_get_guild_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get guild statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_chan, -1, &m_set_chan_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set channel statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_chan, -1, &m_get_chan_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get channel statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_ban, -1, &m_set_ban_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set ban statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_ban, -1, &m_get_ban_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get ban statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, clear_ban, -1, &m_clear_ban_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare clear ban statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_bans, -1, &m_get_bans_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get bans statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, set_interaction, -1, &m_set_msg_interaction_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare set message interaction statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_last_msgs, -1, &m_get_last_msgs_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get last messages statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_msg_ids, -1, &m_get_msg_ids_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get msg ids statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_pins, -1, &m_get_pins_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get pins statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, get_threads, -1, &m_get_threads_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare get threads statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- m_db_err = sqlite3_prepare_v2(m_db, clear_chan, -1, &m_clear_chan_stmt, nullptr);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "failed to prepare clear channel statement: %s\n", sqlite3_errstr(m_db_err));
- return false;
- }
-
- return true;
-}
-
-void Store::Cleanup() {
- sqlite3_finalize(m_set_user_stmt);
- sqlite3_finalize(m_get_user_stmt);
- sqlite3_finalize(m_set_perm_stmt);
- sqlite3_finalize(m_get_perm_stmt);
- sqlite3_finalize(m_set_msg_stmt);
- sqlite3_finalize(m_get_msg_stmt);
- sqlite3_finalize(m_set_role_stmt);
- sqlite3_finalize(m_get_role_stmt);
- sqlite3_finalize(m_set_emote_stmt);
- sqlite3_finalize(m_get_emote_stmt);
- sqlite3_finalize(m_set_member_stmt);
- sqlite3_finalize(m_get_member_stmt);
- sqlite3_finalize(m_set_guild_stmt);
- sqlite3_finalize(m_get_guild_stmt);
- sqlite3_finalize(m_set_chan_stmt);
- sqlite3_finalize(m_get_chan_stmt);
- sqlite3_finalize(m_set_ban_stmt);
- sqlite3_finalize(m_get_ban_stmt);
- sqlite3_finalize(m_clear_ban_stmt);
- sqlite3_finalize(m_get_bans_stmt);
- sqlite3_finalize(m_set_msg_interaction_stmt);
- sqlite3_finalize(m_get_last_msgs_stmt);
- sqlite3_finalize(m_get_msg_ids_stmt);
- sqlite3_finalize(m_get_pins_stmt);
- sqlite3_finalize(m_get_threads_stmt);
- sqlite3_finalize(m_clear_chan_stmt);
-}
-
-void Store::Bind(sqlite3_stmt *stmt, int index, int num) const {
- m_db_err = sqlite3_bind_int(stmt, index, num);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
- }
-}
-
-void Store::Bind(sqlite3_stmt *stmt, int index, uint64_t num) const {
- m_db_err = sqlite3_bind_int64(stmt, index, num);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
- }
-}
-
-void Store::Bind(sqlite3_stmt *stmt, int index, const std::string &str) const {
- m_db_err = sqlite3_bind_blob(stmt, index, str.c_str(), str.length(), SQLITE_TRANSIENT);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
- }
-}
-
-void Store::Bind(sqlite3_stmt *stmt, int index, bool val) const {
- m_db_err = sqlite3_bind_int(stmt, index, val ? 1 : 0);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
- }
-}
-
-void Store::Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const {
- m_db_err = sqlite3_bind_null(stmt, index);
- if (m_db_err != SQLITE_OK) {
- fprintf(stderr, "error binding index %d: %s\n", index, sqlite3_errstr(m_db_err));
- }
-}
-
-bool Store::RunInsert(sqlite3_stmt *stmt) {
- m_db_err = sqlite3_step(stmt);
- Reset(stmt);
- return m_db_err == SQLITE_DONE;
-}
-
-bool Store::FetchOne(sqlite3_stmt *stmt) const {
- m_db_err = sqlite3_step(stmt);
- return m_db_err == SQLITE_ROW;
-}
-
-void Store::Get(sqlite3_stmt *stmt, int index, int &out) const {
- out = sqlite3_column_int(stmt, index);
-}
-
-void Store::Get(sqlite3_stmt *stmt, int index, uint64_t &out) const {
- out = sqlite3_column_int64(stmt, index);
-}
-
-void Store::Get(sqlite3_stmt *stmt, int index, std::string &out) const {
- const unsigned char *ptr = sqlite3_column_text(stmt, index);
- if (ptr == nullptr)
- out = "";
- else
- out = reinterpret_cast<const char *>(ptr);
-}
-
-void Store::Get(sqlite3_stmt *stmt, int index, bool &out) const {
- out = sqlite3_column_int(stmt, index) != 0;
-}
-
-void Store::Get(sqlite3_stmt *stmt, int index, Snowflake &out) const {
- const int64_t num = sqlite3_column_int64(stmt, index);
- out = static_cast<uint64_t>(num);
-}
-
-bool Store::IsNull(sqlite3_stmt *stmt, int index) const {
- return sqlite3_column_type(stmt, index) == SQLITE_NULL;
-}
-
-void Store::Reset(sqlite3_stmt *stmt) const {
- sqlite3_reset(stmt);
- sqlite3_clear_bindings(stmt);
-}
diff --git a/discord/store.hpp b/discord/store.hpp
deleted file mode 100644
index f84d13e..0000000
--- a/discord/store.hpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#pragma once
-#include "../util.hpp"
-#include "objects.hpp"
-#include <unordered_map>
-#include <unordered_set>
-#include <mutex>
-#include <filesystem>
-#include <sqlite3.h>
-
-#ifdef GetMessage // fuck you windows.h
- #undef GetMessage
-#endif
-
-class Store {
-public:
- Store(bool mem_store = false);
- ~Store();
-
- bool IsValid() const;
-
- void SetUser(Snowflake id, const UserData &user);
- void SetChannel(Snowflake id, const ChannelData &chan);
- void SetGuild(Snowflake id, const GuildData &guild);
- void SetRole(Snowflake id, const RoleData &role);
- void SetMessage(Snowflake id, const Message &message);
- void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
- void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
- void SetEmoji(Snowflake id, const EmojiData &emoji);
- void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
-
- // slap const on everything even tho its not *really* const
-
- std::optional<ChannelData> GetChannel(Snowflake id) const;
- std::optional<EmojiData> GetEmoji(Snowflake id) const;
- std::optional<GuildData> GetGuild(Snowflake id) const;
- std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const;
- std::optional<Message> GetMessage(Snowflake id) const;
- std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
- std::optional<RoleData> GetRole(Snowflake id) const;
- std::optional<UserData> GetUser(Snowflake id) const;
- std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
- std::vector<BanData> GetBans(Snowflake guild_id) const;
-
- std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
- std::vector<Snowflake> GetChannelMessageIDs(Snowflake id) const;
- std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
- std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
-
- void ClearGuild(Snowflake id);
- void ClearChannel(Snowflake id);
- void ClearBan(Snowflake guild_id, Snowflake user_id);
-
- using users_type = std::unordered_map<Snowflake, UserData>;
- using channels_type = std::unordered_map<Snowflake, ChannelData>;
- using guilds_type = std::unordered_map<Snowflake, GuildData>;
- using roles_type = std::unordered_map<Snowflake, RoleData>;
- using messages_type = std::unordered_map<Snowflake, Message>;
- using members_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, GuildMember>>; // [guild][user]
- using permission_overwrites_type = std::unordered_map<Snowflake, std::unordered_map<Snowflake, PermissionOverwrite>>; // [channel][user/role]
- using emojis_type = std::unordered_map<Snowflake, EmojiData>;
-
- const std::unordered_set<Snowflake> &GetChannels() const;
- const std::unordered_set<Snowflake> &GetGuilds() const;
-
- void ClearAll();
-
- void BeginTransaction();
- void EndTransaction();
-
-private:
- Message GetMessageBound(sqlite3_stmt *stmt) const;
-
- void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
-
- std::unordered_set<Snowflake> m_channels;
- std::unordered_set<Snowflake> m_guilds;
-
- bool CreateTables();
- bool CreateStatements();
- void Cleanup();
-
- template<typename T>
- void Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const;
-
- template<typename T>
- typename std::enable_if<std::is_enum<T>::value, void>::type
- Bind(sqlite3_stmt *stmt, int index, T val) const;
-
- void Bind(sqlite3_stmt *stmt, int index, int num) const;
- void Bind(sqlite3_stmt *stmt, int index, uint64_t num) const;
- void Bind(sqlite3_stmt *stmt, int index, const std::string &str) const;
- void Bind(sqlite3_stmt *stmt, int index, bool val) const;
- void Bind(sqlite3_stmt *stmt, int index, std::nullptr_t) const;
- bool RunInsert(sqlite3_stmt *stmt);
- bool FetchOne(sqlite3_stmt *stmt) const;
-
- template<typename T>
- void Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const;
-
- template<typename T>
- typename std::enable_if<std::is_enum<T>::value, void>::type
- Get(sqlite3_stmt *stmt, int index, T &out) const;
-
- void Get(sqlite3_stmt *stmt, int index, int &out) const;
- void Get(sqlite3_stmt *stmt, int index, uint64_t &out) const;
- void Get(sqlite3_stmt *stmt, int index, std::string &out) const;
- void Get(sqlite3_stmt *stmt, int index, bool &out) const;
- void Get(sqlite3_stmt *stmt, int index, Snowflake &out) const;
- bool IsNull(sqlite3_stmt *stmt, int index) const;
- void Reset(sqlite3_stmt *stmt) const;
-
- std::filesystem::path m_db_path;
- mutable sqlite3 *m_db;
- mutable int m_db_err;
- mutable sqlite3_stmt *m_set_user_stmt;
- mutable sqlite3_stmt *m_get_user_stmt;
- mutable sqlite3_stmt *m_set_perm_stmt;
- mutable sqlite3_stmt *m_get_perm_stmt;
- mutable sqlite3_stmt *m_set_msg_stmt;
- mutable sqlite3_stmt *m_get_msg_stmt;
- mutable sqlite3_stmt *m_set_role_stmt;
- mutable sqlite3_stmt *m_get_role_stmt;
- mutable sqlite3_stmt *m_set_emote_stmt;
- mutable sqlite3_stmt *m_get_emote_stmt;
- mutable sqlite3_stmt *m_set_member_stmt;
- mutable sqlite3_stmt *m_get_member_stmt;
- mutable sqlite3_stmt *m_set_guild_stmt;
- mutable sqlite3_stmt *m_get_guild_stmt;
- mutable sqlite3_stmt *m_set_chan_stmt;
- mutable sqlite3_stmt *m_get_chan_stmt;
- mutable sqlite3_stmt *m_set_ban_stmt;
- mutable sqlite3_stmt *m_get_ban_stmt;
- mutable sqlite3_stmt *m_clear_ban_stmt;
- mutable sqlite3_stmt *m_get_bans_stmt;
- mutable sqlite3_stmt *m_set_msg_interaction_stmt;
- mutable sqlite3_stmt *m_get_last_msgs_stmt;
- mutable sqlite3_stmt *m_get_msg_ids_stmt;
- mutable sqlite3_stmt *m_get_pins_stmt;
- mutable sqlite3_stmt *m_get_threads_stmt;
- mutable sqlite3_stmt *m_clear_chan_stmt;
-};
-
-template<typename T>
-inline void Store::Bind(sqlite3_stmt *stmt, int index, const std::optional<T> &opt) const {
- if (opt.has_value())
- Bind(stmt, index, *opt);
- else
- sqlite3_bind_null(stmt, index);
-}
-
-template<typename T>
-inline typename std::enable_if<std::is_enum<T>::value, void>::type
-Store::Bind(sqlite3_stmt *stmt, int index, T val) const {
- Bind(stmt, index, static_cast<typename std::underlying_type<T>::type>(val));
-}
-
-template<typename T>
-inline void Store::Get(sqlite3_stmt *stmt, int index, std::optional<T> &out) const {
- if (sqlite3_column_type(stmt, index) == SQLITE_NULL)
- out = std::nullopt;
- else {
- T v;
- Get(stmt, index, v);
- out = std::optional<T>(v);
- }
-}
-
-template<typename T>
-inline typename std::enable_if<std::is_enum<T>::value, void>::type
-Store::Get(sqlite3_stmt *stmt, int index, T &out) const {
- out = static_cast<T>(sqlite3_column_int(stmt, index));
-}
diff --git a/css/application-low-priority.css b/res/css/application-low-priority.css
index cf108f4..cf108f4 100644
--- a/css/application-low-priority.css
+++ b/res/css/application-low-priority.css
diff --git a/css/bare.css b/res/css/bare.css
index 80ef2f8..80ef2f8 100644
--- a/css/bare.css
+++ b/res/css/bare.css
diff --git a/css/main.css b/res/css/main.css
index ee60cd1..ee60cd1 100644
--- a/css/main.css
+++ b/res/css/main.css
diff --git a/fonts/TwitterColorEmoji.ttf b/res/fonts/TwitterColorEmoji.ttf
index 3e41671..3e41671 100644
--- a/fonts/TwitterColorEmoji.ttf
+++ b/res/fonts/TwitterColorEmoji.ttf
Binary files differ
diff --git a/fonts/conf.d/10-autohint.conf b/res/fonts/conf.d/10-autohint.conf
index f9032ba..f9032ba 100644
--- a/fonts/conf.d/10-autohint.conf
+++ b/res/fonts/conf.d/10-autohint.conf
diff --git a/fonts/conf.d/10-hinting-full.conf b/res/fonts/conf.d/10-hinting-full.conf
index 27d8229..27d8229 100644
--- a/fonts/conf.d/10-hinting-full.conf
+++ b/res/fonts/conf.d/10-hinting-full.conf
diff --git a/fonts/conf.d/10-hinting-medium.conf b/res/fonts/conf.d/10-hinting-medium.conf
index e34ab5b..e34ab5b 100644
--- a/fonts/conf.d/10-hinting-medium.conf
+++ b/res/fonts/conf.d/10-hinting-medium.conf
diff --git a/fonts/conf.d/10-hinting-none.conf b/res/fonts/conf.d/10-hinting-none.conf
index 0b3810d..0b3810d 100644
--- a/fonts/conf.d/10-hinting-none.conf
+++ b/res/fonts/conf.d/10-hinting-none.conf
diff --git a/fonts/conf.d/10-hinting-slight.conf b/res/fonts/conf.d/10-hinting-slight.conf
index c244ac0..c244ac0 100644
--- a/fonts/conf.d/10-hinting-slight.conf
+++ b/res/fonts/conf.d/10-hinting-slight.conf
diff --git a/fonts/conf.d/10-no-sub-pixel.conf b/res/fonts/conf.d/10-no-sub-pixel.conf
index 635847c..635847c 100644
--- a/fonts/conf.d/10-no-sub-pixel.conf
+++ b/res/fonts/conf.d/10-no-sub-pixel.conf
diff --git a/fonts/conf.d/10-scale-bitmap-fonts.conf b/res/fonts/conf.d/10-scale-bitmap-fonts.conf
index b20b52e..b20b52e 100644
--- a/fonts/conf.d/10-scale-bitmap-fonts.conf
+++ b/res/fonts/conf.d/10-scale-bitmap-fonts.conf
diff --git a/fonts/conf.d/10-sub-pixel-bgr.conf b/res/fonts/conf.d/10-sub-pixel-bgr.conf
index 1378195..1378195 100644
--- a/fonts/conf.d/10-sub-pixel-bgr.conf
+++ b/res/fonts/conf.d/10-sub-pixel-bgr.conf
diff --git a/fonts/conf.d/10-sub-pixel-rgb.conf b/res/fonts/conf.d/10-sub-pixel-rgb.conf
index 757c3b6..757c3b6 100644
--- a/fonts/conf.d/10-sub-pixel-rgb.conf
+++ b/res/fonts/conf.d/10-sub-pixel-rgb.conf
diff --git a/fonts/conf.d/10-sub-pixel-vbgr.conf b/res/fonts/conf.d/10-sub-pixel-vbgr.conf
index c807e5c..c807e5c 100644
--- a/fonts/conf.d/10-sub-pixel-vbgr.conf
+++ b/res/fonts/conf.d/10-sub-pixel-vbgr.conf
diff --git a/fonts/conf.d/10-sub-pixel-vrgb.conf b/res/fonts/conf.d/10-sub-pixel-vrgb.conf
index b5985e0..b5985e0 100644
--- a/fonts/conf.d/10-sub-pixel-vrgb.conf
+++ b/res/fonts/conf.d/10-sub-pixel-vrgb.conf
diff --git a/fonts/conf.d/10-unhinted.conf b/res/fonts/conf.d/10-unhinted.conf
index a25e3b1..a25e3b1 100644
--- a/fonts/conf.d/10-unhinted.conf
+++ b/res/fonts/conf.d/10-unhinted.conf
diff --git a/fonts/conf.d/11-lcdfilter-default.conf b/res/fonts/conf.d/11-lcdfilter-default.conf
index 9d7d11f..9d7d11f 100644
--- a/fonts/conf.d/11-lcdfilter-default.conf
+++ b/res/fonts/conf.d/11-lcdfilter-default.conf
diff --git a/fonts/conf.d/11-lcdfilter-legacy.conf b/res/fonts/conf.d/11-lcdfilter-legacy.conf
index 7e2b256..7e2b256 100644
--- a/fonts/conf.d/11-lcdfilter-legacy.conf
+++ b/res/fonts/conf.d/11-lcdfilter-legacy.conf
diff --git a/fonts/conf.d/11-lcdfilter-light.conf b/res/fonts/conf.d/11-lcdfilter-light.conf
index 0dd0a90..0dd0a90 100644
--- a/fonts/conf.d/11-lcdfilter-light.conf
+++ b/res/fonts/conf.d/11-lcdfilter-light.conf
diff --git a/fonts/conf.d/20-unhint-small-vera.conf b/res/fonts/conf.d/20-unhint-small-vera.conf
index c9505ca..c9505ca 100644
--- a/fonts/conf.d/20-unhint-small-vera.conf
+++ b/res/fonts/conf.d/20-unhint-small-vera.conf
diff --git a/fonts/conf.d/25-unhint-nonlatin.conf b/res/fonts/conf.d/25-unhint-nonlatin.conf
index 9adffa7..9adffa7 100644
--- a/fonts/conf.d/25-unhint-nonlatin.conf
+++ b/res/fonts/conf.d/25-unhint-nonlatin.conf
diff --git a/fonts/conf.d/30-metric-aliases.conf b/res/fonts/conf.d/30-metric-aliases.conf
index 1f0778d..1f0778d 100644
--- a/fonts/conf.d/30-metric-aliases.conf
+++ b/res/fonts/conf.d/30-metric-aliases.conf
diff --git a/fonts/conf.d/30-urw-aliases.conf b/res/fonts/conf.d/30-urw-aliases.conf
index cfde071..cfde071 100644
--- a/fonts/conf.d/30-urw-aliases.conf
+++ b/res/fonts/conf.d/30-urw-aliases.conf
diff --git a/fonts/conf.d/40-nonlatin.conf b/res/fonts/conf.d/40-nonlatin.conf
index c900fd4..c900fd4 100644
--- a/fonts/conf.d/40-nonlatin.conf
+++ b/res/fonts/conf.d/40-nonlatin.conf
diff --git a/fonts/conf.d/45-latin.conf b/res/fonts/conf.d/45-latin.conf
index c6696f8..c6696f8 100644
--- a/fonts/conf.d/45-latin.conf
+++ b/res/fonts/conf.d/45-latin.conf
diff --git a/fonts/conf.d/49-sansserif.conf b/res/fonts/conf.d/49-sansserif.conf
index c6209a7..c6209a7 100644
--- a/fonts/conf.d/49-sansserif.conf
+++ b/res/fonts/conf.d/49-sansserif.conf
diff --git a/fonts/conf.d/50-user.conf b/res/fonts/conf.d/50-user.conf
index 07c9182..07c9182 100644
--- a/fonts/conf.d/50-user.conf
+++ b/res/fonts/conf.d/50-user.conf
diff --git a/fonts/conf.d/51-local.conf b/res/fonts/conf.d/51-local.conf
index ca9fbe1..ca9fbe1 100644
--- a/fonts/conf.d/51-local.conf
+++ b/res/fonts/conf.d/51-local.conf
diff --git a/fonts/conf.d/55-emoji-prepend.conf b/res/fonts/conf.d/55-emoji-prepend.conf
index b56cebb..b56cebb 100644
--- a/fonts/conf.d/55-emoji-prepend.conf
+++ b/res/fonts/conf.d/55-emoji-prepend.conf
diff --git a/fonts/conf.d/60-latin.conf b/res/fonts/conf.d/60-latin.conf
index 23ee91b..23ee91b 100644
--- a/fonts/conf.d/60-latin.conf
+++ b/res/fonts/conf.d/60-latin.conf
diff --git a/fonts/conf.d/65-fonts-persian.conf b/res/fonts/conf.d/65-fonts-persian.conf
index ea00661..ea00661 100644
--- a/fonts/conf.d/65-fonts-persian.conf
+++ b/res/fonts/conf.d/65-fonts-persian.conf
diff --git a/fonts/conf.d/65-khmer.conf b/res/fonts/conf.d/65-khmer.conf
index f9d06f2..f9d06f2 100644
--- a/fonts/conf.d/65-khmer.conf
+++ b/res/fonts/conf.d/65-khmer.conf
diff --git a/fonts/conf.d/65-nonlatin.conf b/res/fonts/conf.d/65-nonlatin.conf
index 9306f74..9306f74 100644
--- a/fonts/conf.d/65-nonlatin.conf
+++ b/res/fonts/conf.d/65-nonlatin.conf
diff --git a/fonts/conf.d/69-unifont.conf b/res/fonts/conf.d/69-unifont.conf
index 177dec5..177dec5 100644
--- a/fonts/conf.d/69-unifont.conf
+++ b/res/fonts/conf.d/69-unifont.conf
diff --git a/fonts/conf.d/70-yes-bitmaps.conf b/res/fonts/conf.d/70-yes-bitmaps.conf
index c153aeb..c153aeb 100644
--- a/fonts/conf.d/70-yes-bitmaps.conf
+++ b/res/fonts/conf.d/70-yes-bitmaps.conf
diff --git a/fonts/conf.d/80-delicious.conf b/res/fonts/conf.d/80-delicious.conf
index 845647b..845647b 100644
--- a/fonts/conf.d/80-delicious.conf
+++ b/res/fonts/conf.d/80-delicious.conf
diff --git a/fonts/conf.d/90-synthetic.conf b/res/fonts/conf.d/90-synthetic.conf
index b8d1e85..b8d1e85 100644
--- a/fonts/conf.d/90-synthetic.conf
+++ b/res/fonts/conf.d/90-synthetic.conf
diff --git a/fonts/fonts.template.conf b/res/fonts/fonts.template.conf
index 8c79974..8c79974 100644
--- a/fonts/fonts.template.conf
+++ b/res/fonts/fonts.template.conf
diff --git a/res/battlenet.png b/res/res/battlenet.png
index 5defc7f..5defc7f 100644
--- a/res/battlenet.png
+++ b/res/res/battlenet.png
Binary files differ
diff --git a/res/certifiedmoderator.png b/res/res/certifiedmoderator.png
index 7963766..7963766 100644
--- a/res/certifiedmoderator.png
+++ b/res/res/certifiedmoderator.png
Binary files differ
diff --git a/res/checkmark.png b/res/res/checkmark.png
index dc8db02..dc8db02 100644
--- a/res/checkmark.png
+++ b/res/res/checkmark.png
Binary files differ
diff --git a/res/clock.png b/res/res/clock.png
index e775451..e775451 100644
--- a/res/clock.png
+++ b/res/res/clock.png
Binary files differ
diff --git a/res/crown.png b/res/res/crown.png
index 6038716..6038716 100644
--- a/res/crown.png
+++ b/res/res/crown.png
Binary files differ
diff --git a/res/decamarks.png b/res/res/decamarks.png
index 972ec66..972ec66 100644
--- a/res/decamarks.png
+++ b/res/res/decamarks.png
Binary files differ
diff --git a/res/discordbughunter.png b/res/res/discordbughunter.png
index de8f945..de8f945 100644
--- a/res/discordbughunter.png
+++ b/res/res/discordbughunter.png
Binary files differ
diff --git a/res/discordbughunter2.png b/res/res/discordbughunter2.png
index 0d40493..0d40493 100644
--- a/res/discordbughunter2.png
+++ b/res/res/discordbughunter2.png
Binary files differ
diff --git a/res/discordstaff.png b/res/res/discordstaff.png
index 4bbd511..4bbd511 100644
--- a/res/discordstaff.png
+++ b/res/res/discordstaff.png
Binary files differ
diff --git a/res/earlysupporter.png b/res/res/earlysupporter.png
index 72e3e70..72e3e70 100644
--- a/res/earlysupporter.png
+++ b/res/res/earlysupporter.png
Binary files differ
diff --git a/res/earlyverifiedbotdeveloper.png b/res/res/earlyverifiedbotdeveloper.png
index 178adb3..178adb3 100644
--- a/res/earlyverifiedbotdeveloper.png
+++ b/res/res/earlyverifiedbotdeveloper.png
Binary files differ
diff --git a/res/emojis.bin b/res/res/emojis.bin
index 7694b20..7694b20 100644
--- a/res/emojis.bin
+++ b/res/res/emojis.bin
Binary files differ
diff --git a/res/facebook.png b/res/res/facebook.png
index b508ecd..b508ecd 100644
--- a/res/facebook.png
+++ b/res/res/facebook.png
Binary files differ
diff --git a/res/github.png b/res/res/github.png
index d70d5e1..d70d5e1 100644
--- a/res/github.png
+++ b/res/res/github.png
Binary files differ
diff --git a/res/guildsubscriber.png b/res/res/guildsubscriber.png
index 6e8236e..6e8236e 100644
--- a/res/guildsubscriber.png
+++ b/res/res/guildsubscriber.png
Binary files differ
diff --git a/res/hypesquadbalance.png b/res/res/hypesquadbalance.png
index 66d924c..66d924c 100644
--- a/res/hypesquadbalance.png
+++ b/res/res/hypesquadbalance.png
Binary files differ
diff --git a/res/hypesquadbravery.png b/res/res/hypesquadbravery.png
index 0feaa04..0feaa04 100644
--- a/res/hypesquadbravery.png
+++ b/res/res/hypesquadbravery.png
Binary files differ
diff --git a/res/hypesquadbrilliance.png b/res/res/hypesquadbrilliance.png
index caf005e..caf005e 100644
--- a/res/hypesquadbrilliance.png
+++ b/res/res/hypesquadbrilliance.png
Binary files differ
diff --git a/res/hypesquadevents.png b/res/res/hypesquadevents.png
index cfa3c86..cfa3c86 100644
--- a/res/hypesquadevents.png
+++ b/res/res/hypesquadevents.png
Binary files differ
diff --git a/res/leagueoflegends.png b/res/res/leagueoflegends.png
index 7fe3edf..7fe3edf 100644
--- a/res/leagueoflegends.png
+++ b/res/res/leagueoflegends.png
Binary files differ
diff --git a/res/partneredowner.png b/res/res/partneredowner.png
index 2a7ed6d..2a7ed6d 100644
--- a/res/partneredowner.png
+++ b/res/res/partneredowner.png
Binary files differ
diff --git a/res/premium.png b/res/res/premium.png
index 30411ac..30411ac 100644
--- a/res/premium.png
+++ b/res/res/premium.png
Binary files differ
diff --git a/res/reddit.png b/res/res/reddit.png
index 538f851..538f851 100644
--- a/res/reddit.png
+++ b/res/res/reddit.png
Binary files differ
diff --git a/res/skype.png b/res/res/skype.png
index f89c73f..f89c73f 100644
--- a/res/skype.png
+++ b/res/res/skype.png
Binary files differ
diff --git a/res/spotify.png b/res/res/spotify.png
index 2893309..2893309 100644
--- a/res/spotify.png
+++ b/res/res/spotify.png
Binary files differ
diff --git a/res/steam.png b/res/res/steam.png
index 70add21..70add21 100644
--- a/res/steam.png
+++ b/res/res/steam.png
Binary files differ
diff --git a/res/twitch.png b/res/res/twitch.png
index a3eed31..a3eed31 100644
--- a/res/twitch.png
+++ b/res/res/twitch.png
Binary files differ
diff --git a/res/twitter.png b/res/res/twitter.png
index 610ae24..610ae24 100644
--- a/res/twitter.png
+++ b/res/res/twitter.png
Binary files differ
diff --git a/res/typing_indicator.gif b/res/res/typing_indicator.gif
index 3d3c59f..3d3c59f 100644
--- a/res/typing_indicator.gif
+++ b/res/res/typing_indicator.gif
Binary files differ
diff --git a/res/xbox.png b/res/res/xbox.png
index 582c6e0..582c6e0 100644
--- a/res/xbox.png
+++ b/res/res/xbox.png
Binary files differ
diff --git a/res/youtube.png b/res/res/youtube.png
index bdc2739..bdc2739 100644
--- a/res/youtube.png
+++ b/res/res/youtube.png
Binary files differ
diff --git a/settings.cpp b/settings.cpp
deleted file mode 100644
index 0a7dbb7..0000000
--- a/settings.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-#include "settings.hpp"
-#include <filesystem>
-#include <fstream>
-
-SettingsManager::SettingsManager(std::string filename)
- : m_filename(filename) {
- if (!std::filesystem::exists(filename)) {
- std::fstream fs;
- fs.open(filename, std::ios::out);
- fs.close();
- }
-
- auto rc = m_ini.LoadFile(filename.c_str());
- m_ok = rc == SI_OK;
-}
-
-void SettingsManager::Reload() {
- m_ok = m_ini.LoadFile(m_filename.c_str()) == SI_OK;
-}
-
-std::string SettingsManager::GetSettingString(const std::string &section, const std::string &key, std::string fallback) const {
- return m_ini.GetValue(section.c_str(), key.c_str(), fallback.c_str());
-}
-
-int SettingsManager::GetSettingInt(const std::string &section, const std::string &key, int fallback) const {
- return std::stoul(GetSettingString(section, key, std::to_string(fallback)));
-}
-
-bool SettingsManager::GetSettingBool(const std::string &section, const std::string &key, bool fallback) const {
- return GetSettingString(section, key, fallback ? "true" : "false") != "false";
-}
-
-bool SettingsManager::IsValid() const {
- return m_ok;
-}
-
-void SettingsManager::Close() {
- m_ini.SaveFile(m_filename.c_str());
-}
-
-bool SettingsManager::GetUseMemoryDB() const {
- return GetSettingBool("discord", "memory_db", false);
-}
-
-std::string SettingsManager::GetUserAgent() const {
- return GetSettingString("http", "user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36");
-}
-
-std::string SettingsManager::GetDiscordToken() const {
- return GetSettingString("discord", "token");
-}
-
-bool SettingsManager::GetShowMemberListDiscriminators() const {
- return GetSettingBool("gui", "member_list_discriminator", true);
-}
-
-bool SettingsManager::GetShowStockEmojis() const {
-#ifdef _WIN32
- return GetSettingBool("gui", "stock_emojis", false);
-#else
- return GetSettingBool("gui", "stock_emojis", true);
-#endif
-}
-
-bool SettingsManager::GetShowCustomEmojis() const {
- return GetSettingBool("gui", "custom_emojis", true);
-}
-
-std::string SettingsManager::GetLinkColor() const {
- return GetSettingString("style", "linkcolor", "rgba(40, 200, 180, 255)");
-}
-
-std::string SettingsManager::GetChannelsExpanderColor() const {
- return GetSettingString("style", "expandercolor", "rgba(255, 83, 112, 255)");
-}
-
-std::string SettingsManager::GetNSFWChannelColor() const {
- return GetSettingString("style", "nsfwchannelcolor", "#ed6666");
-}
-
-int SettingsManager::GetCacheHTTPConcurrency() const {
- return GetSettingInt("http", "concurrent", 20);
-}
-
-bool SettingsManager::GetPrefetch() const {
- return GetSettingBool("discord", "prefetch", false);
-}
-
-std::string SettingsManager::GetMainCSS() const {
- return GetSettingString("gui", "css", "main.css");
-}
-
-bool SettingsManager::GetShowAnimations() const {
- return GetSettingBool("gui", "animations", true);
-}
-
-bool SettingsManager::GetShowOwnerCrown() const {
- return GetSettingBool("gui", "owner_crown", true);
-}
-
-std::string SettingsManager::GetGatewayURL() const {
- return GetSettingString("discord", "gateway", "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream");
-}
-
-std::string SettingsManager::GetAPIBaseURL() const {
- return GetSettingString("discord", "api_base", "https://discord.com/api/v9");
-}
-
-bool SettingsManager::GetAnimatedGuildHoverOnly() const {
- return GetSettingBool("gui", "animated_guild_hover_only", true);
-}
-
-bool SettingsManager::GetSaveState() const {
- return GetSettingBool("gui", "save_state", true);
-}
diff --git a/settings.hpp b/settings.hpp
deleted file mode 100644
index 3fff593..0000000
--- a/settings.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-#pragma once
-#include <string>
-#include <type_traits>
-#include <SimpleIni.h>
-
-class SettingsManager {
-public:
- SettingsManager(std::string filename);
- void Reload();
-
- void Close();
- bool GetUseMemoryDB() const;
- std::string GetUserAgent() const;
- std::string GetDiscordToken() const;
- bool GetShowMemberListDiscriminators() const;
- bool GetShowStockEmojis() const;
- bool GetShowCustomEmojis() const;
- int GetCacheHTTPConcurrency() const;
- bool GetPrefetch() const;
- std::string GetMainCSS() const;
- bool GetShowAnimations() const;
- bool GetShowOwnerCrown() const;
- std::string GetGatewayURL() const;
- std::string GetAPIBaseURL() const;
- bool GetAnimatedGuildHoverOnly() const;
- bool GetSaveState() const;
-
- // i would like to use Gtk::StyleProperty for this, but it will not work on windows
- // #1 it's missing from the project files for the version used by vcpkg
- // #2 it's still broken and doesn't function even when added to the solution
- // #3 it's a massive pain in the ass to try and bump the version to a functioning version
- // because they switch build systems to nmake/meson (took months to get merged in vcpkg)
- // #4 c++ build systems sucks
- // three options are: use gtk4 with updated vcpkg, try and port it myself, or use msys2 instead of vcpkg
- // im leaning towards msys
- std::string GetLinkColor() const;
- std::string GetChannelsExpanderColor() const;
- std::string GetNSFWChannelColor() const;
-
- bool IsValid() const;
-
- template<typename T>
- void SetSetting(std::string section, std::string key, T value) {
- m_ini.SetValue(section.c_str(), key.c_str(), std::to_string(value).c_str());
- m_ini.SaveFile(m_filename.c_str());
- }
-
- void SetSetting(std::string section, std::string key, std::string value) {
- m_ini.SetValue(section.c_str(), key.c_str(), value.c_str());
- m_ini.SaveFile(m_filename.c_str());
- }
-
-private:
- std::string GetSettingString(const std::string &section, const std::string &key, std::string fallback = "") const;
- int GetSettingInt(const std::string &section, const std::string &key, int fallback) const;
- bool GetSettingBool(const std::string &section, const std::string &key, bool fallback) const;
-
-private:
- bool m_ok;
- std::string m_filename;
- CSimpleIniA m_ini;
-};
diff --git a/MurmurHash3.cpp b/src/MurmurHash3.cpp
index f26ffa1..f26ffa1 100644
--- a/MurmurHash3.cpp
+++ b/src/MurmurHash3.cpp
diff --git a/MurmurHash3.h b/src/MurmurHash3.h
index c28c12d..c28c12d 100644
--- a/MurmurHash3.h
+++ b/src/MurmurHash3.h
diff --git a/abaddon.cpp b/src/abaddon.cpp
index a0c83d7..db8c02a 100644
--- a/abaddon.cpp
+++ b/src/abaddon.cpp
@@ -23,12 +23,12 @@
Abaddon::Abaddon()
: m_settings(Platform::FindConfigFile())
- , m_discord(m_settings.GetUseMemoryDB()) // stupid but easy
+ , m_discord(GetSettings().UseMemoryDB) // stupid but easy
, m_emojis(GetResPath("/emojis.bin")) {
LoadFromSettings();
// todo: set user agent for non-client(?)
- std::string ua = m_settings.GetUserAgent();
+ std::string ua = GetSettings().UserAgent;
m_discord.SetUserAgent(ua);
m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady));
@@ -43,7 +43,7 @@ Abaddon::Abaddon()
m_discord.signal_thread_update().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnThreadUpdate));
m_discord.signal_message_sent().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnMessageSent));
m_discord.signal_disconnected().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnDisconnect));
- if (m_settings.GetPrefetch())
+ if (GetSettings().Prefetch)
m_discord.signal_message_create().connect([this](const Message &message) {
if (message.Author.HasAvatar())
m_img_mgr.Prefetch(message.Author.GetAvatarURL());
@@ -54,10 +54,6 @@ Abaddon::Abaddon()
});
}
-Abaddon::~Abaddon() {
- m_settings.Close();
-}
-
Abaddon &Abaddon::Get() {
static Abaddon instance;
return instance;
@@ -82,9 +78,30 @@ int Abaddon::StartGTK() {
m_main_window = std::make_unique<MainWindow>();
m_main_window->set_title(APP_TITLE);
- m_main_window->UpdateComponents();
m_main_window->set_position(Gtk::WIN_POS_CENTER);
+ if (!m_settings.IsValid()) {
+ Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be opened!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.set_position(Gtk::WIN_POS_CENTER);
+ dlg.run();
+ }
+
+ if (!m_emojis.Load()) {
+ Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.set_position(Gtk::WIN_POS_CENTER);
+ dlg.run();
+ }
+
+ if (!m_discord.IsStoreValid()) {
+ Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
+ dlg.set_position(Gtk::WIN_POS_CENTER);
+ dlg.run();
+ return 1;
+ }
+
+ // store must be checked before this can be called
+ m_main_window->UpdateComponents();
+
// crashes for some stupid reason if i put it somewhere else
SetupUserMenu();
@@ -112,33 +129,19 @@ int Abaddon::StartGTK() {
ActionReloadCSS();
- m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::StopDiscord), false);
-
- if (!m_settings.IsValid()) {
- Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
- dlg.set_position(Gtk::WIN_POS_CENTER);
- dlg.run();
- }
-
- if (!m_emojis.Load()) {
- Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
- dlg.set_position(Gtk::WIN_POS_CENTER);
- dlg.run();
- }
-
- if (!m_discord.IsStoreValid()) {
- Gtk::MessageDialog dlg(*m_main_window, "The Discord cache could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
- dlg.set_position(Gtk::WIN_POS_CENTER);
- dlg.run();
- return 1;
- }
+ m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false);
m_main_window->show();
return m_gtk_app->run(*m_main_window);
}
+void Abaddon::OnShutdown() {
+ StopDiscord();
+ m_settings.Close();
+}
+
void Abaddon::LoadFromSettings() {
- std::string token = m_settings.GetDiscordToken();
+ std::string token = GetSettings().DiscordToken;
if (token.size()) {
m_discord_token = token;
m_discord.UpdateToken(m_discord_token);
@@ -246,8 +249,8 @@ void Abaddon::DiscordOnThreadUpdate(const ThreadUpdateData &data) {
}
}
-const SettingsManager &Abaddon::GetSettings() const {
- return m_settings;
+SettingsManager::Settings &Abaddon::GetSettings() {
+ return m_settings.GetSettings();
}
Glib::RefPtr<Gtk::CssProvider> Abaddon::GetStyleProvider() {
@@ -365,7 +368,7 @@ void Abaddon::SetupUserMenu() {
}
void Abaddon::SaveState() {
- if (!m_settings.GetSaveState()) return;
+ if (!GetSettings().SaveState) return;
AbaddonApplicationState state;
state.ActiveChannel = m_main_window->GetChatActiveChannel();
@@ -385,7 +388,7 @@ void Abaddon::SaveState() {
}
void Abaddon::LoadState() {
- if (!m_settings.GetSaveState()) return;
+ if (!GetSettings().SaveState) return;
const auto data = ReadWholeFile(GetStateCachePath("/state.json"));
if (data.empty()) return;
@@ -489,7 +492,7 @@ void Abaddon::ActionSetToken() {
m_discord_token = dlg.GetToken();
m_discord.UpdateToken(m_discord_token);
m_main_window->UpdateComponents();
- m_settings.SetSetting("discord", "token", m_discord_token);
+ GetSettings().DiscordToken = m_discord_token;
}
}
@@ -552,16 +555,9 @@ void Abaddon::ActionChatLoadHistory(Snowflake id) {
return;
Snowflake before_id = m_main_window->GetChatOldestListedMessage();
- auto knownset = m_discord.GetMessageIDsForChannel(id);
- std::vector<Snowflake> knownvec(knownset.begin(), knownset.end());
- std::sort(knownvec.begin(), knownvec.end());
- auto latest = std::find_if(knownvec.begin(), knownvec.end(), [&before_id](Snowflake x) -> bool { return x == before_id; });
- int distance = std::distance(knownvec.begin(), latest);
-
- if (distance >= 50) {
- std::vector<Message> msgs;
- for (auto it = knownvec.begin() + distance - 50; it != knownvec.begin() + distance; it++)
- msgs.push_back(*m_discord.GetMessage(*it));
+ auto msgs = m_discord.GetMessagesBefore(id, before_id);
+
+ if (msgs.size() >= 50) {
m_main_window->UpdateChatPrependHistory(msgs);
return;
}
@@ -703,7 +699,7 @@ bool Abaddon::ShowConfirm(const Glib::ustring &prompt, Gtk::Window *window) {
void Abaddon::ActionReloadCSS() {
try {
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_provider);
- m_css_provider->load_from_path(GetCSSPath("/" + m_settings.GetMainCSS()));
+ m_css_provider->load_from_path(GetCSSPath("/" + GetSettings().MainCSS));
Gtk::StyleContext::add_provider_for_screen(Gdk::Screen::get_default(), m_css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
Gtk::StyleContext::remove_provider_for_screen(Gdk::Screen::get_default(), m_css_low_provider);
diff --git a/abaddon.hpp b/src/abaddon.hpp
index 0fb4f1f..d9d0bb0 100644
--- a/abaddon.hpp
+++ b/src/abaddon.hpp
@@ -14,7 +14,6 @@
class Abaddon {
private:
Abaddon();
- ~Abaddon();
Abaddon(const Abaddon &) = delete;
Abaddon &operator=(const Abaddon &) = delete;
Abaddon(Abaddon &&) = delete;
@@ -24,6 +23,8 @@ public:
static Abaddon &Get();
int StartGTK();
+ void OnShutdown();
+
void StartDiscord();
void StopDiscord();
@@ -74,7 +75,7 @@ public:
void DiscordOnDisconnect(bool is_reconnecting, GatewayCloseCode close_code);
void DiscordOnThreadUpdate(const ThreadUpdateData &data);
- const SettingsManager &GetSettings() const;
+ SettingsManager::Settings &GetSettings();
Glib::RefPtr<Gtk::CssProvider> GetStyleProvider();
diff --git a/components/cellrendererpixbufanimation.cpp b/src/components/cellrendererpixbufanimation.cpp
index 2658967..2658967 100644
--- a/components/cellrendererpixbufanimation.cpp
+++ b/src/components/cellrendererpixbufanimation.cpp
diff --git a/components/cellrendererpixbufanimation.hpp b/src/components/cellrendererpixbufanimation.hpp
index f47e928..f47e928 100644
--- a/components/cellrendererpixbufanimation.hpp
+++ b/src/components/cellrendererpixbufanimation.hpp
diff --git a/components/channels.cpp b/src/components/channels.cpp
index b4769d9..6d5e1a6 100644
--- a/components/channels.cpp
+++ b/src/components/channels.cpp
@@ -2,9 +2,9 @@
#include <algorithm>
#include <map>
#include <unordered_map>
-#include "../abaddon.hpp"
-#include "../imgmanager.hpp"
-#include "../util.hpp"
+#include "abaddon.hpp"
+#include "imgmanager.hpp"
+#include "util.hpp"
#include "statusindicator.hpp"
ChannelList::ChannelList()
@@ -263,11 +263,9 @@ void ChannelList::UpdateGuild(Snowflake id) {
const auto guild = Abaddon::Get().GetDiscordClient().GetGuild(id);
if (!iter || !guild.has_value()) return;
- static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
-
(*iter)[m_columns.m_name] = "<b>" + Glib::Markup::escape_text(guild->Name) + "</b>";
(*iter)[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize);
- if (show_animations && guild->HasAnimatedIcon()) {
+ if (Abaddon::Get().GetSettings().ShowAnimations && guild->HasAnimatedIcon()) {
const auto cb = [this, id](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
auto iter = GetIteratorForGuildFromID(id);
if (iter) (*iter)[m_columns.m_icon_anim] = pb;
@@ -436,9 +434,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
guild_row[m_columns.m_name] = "<b>" + Glib::Markup::escape_text(guild.Name) + "</b>";
guild_row[m_columns.m_icon] = img.GetPlaceholder(GuildIconSize);
- static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
-
- if (show_animations && guild.HasAnimatedIcon()) {
+ if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) {
const auto cb = [this, id = guild.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
auto iter = GetIteratorForGuildFromID(id);
if (iter) (*iter)[m_columns.m_icon_anim] = pb;
@@ -998,7 +994,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtr<Cairo::Context
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
- const static bool hover_only = Abaddon::Get().GetSettings().GetAnimatedGuildHoverOnly();
+ const bool hover_only = Abaddon::Get().GetSettings().AnimatedGuildHoverOnly;
const bool is_hovered = flags & Gtk::CELL_RENDERER_PRELIT;
auto anim = m_property_pixbuf_animation.get_value();
@@ -1069,7 +1065,7 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr<Cairo::Cont
cr->move_to(x1, y1);
cr->line_to(x2, y2);
cr->line_to(x3, y3);
- static const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().GetChannelsExpanderColor());
+ const auto expander_color = Gdk::RGBA(Abaddon::Get().GetSettings().ChannelsExpanderColor);
cr->set_source_rgb(expander_color.get_red(), expander_color.get_green(), expander_color.get_blue());
cr->stroke();
@@ -1115,7 +1111,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr<Cairo::Conte
Gdk::Rectangle text_cell_area(text_x, text_y, text_w, text_h);
- const static auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().GetNSFWChannelColor());
+ const auto nsfw_color = Gdk::RGBA(Abaddon::Get().GetSettings().NSFWChannelColor);
if (m_property_nsfw.get_value())
m_renderer_text.property_foreground_rgba() = nsfw_color;
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
diff --git a/components/channels.hpp b/src/components/channels.hpp
index 1faf367..1faf367 100644
--- a/components/channels.hpp
+++ b/src/components/channels.hpp
diff --git a/components/chatinput.cpp b/src/components/chatinput.cpp
index c3eca32..c3eca32 100644
--- a/components/chatinput.cpp
+++ b/src/components/chatinput.cpp
diff --git a/components/chatinput.hpp b/src/components/chatinput.hpp
index ad7f0b1..ad7f0b1 100644
--- a/components/chatinput.hpp
+++ b/src/components/chatinput.hpp
diff --git a/components/chatinputindicator.cpp b/src/components/chatinputindicator.cpp
index acc2aa6..9b063b2 100644
--- a/components/chatinputindicator.cpp
+++ b/src/components/chatinputindicator.cpp
@@ -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;
diff --git a/components/chatinputindicator.hpp b/src/components/chatinputindicator.hpp
index 3b007b3..ec70dfb 100644
--- a/components/chatinputindicator.hpp
+++ b/src/components/chatinputindicator.hpp
@@ -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:
diff --git a/components/chatlist.cpp b/src/components/chatlist.cpp
index 5b3f357..5b3f357 100644
--- a/components/chatlist.cpp
+++ b/src/components/chatlist.cpp
diff --git a/components/chatlist.hpp b/src/components/chatlist.hpp
index e5afb80..e5afb80 100644
--- a/components/chatlist.hpp
+++ b/src/components/chatlist.hpp
diff --git a/components/chatmessage.cpp b/src/components/chatmessage.cpp
index 4408f62..ef972bb 100644
--- a/components/chatmessage.cpp
+++ b/src/components/chatmessage.cpp
@@ -1,6 +1,6 @@
#include "chatmessage.hpp"
-#include "../abaddon.hpp"
-#include "../util.hpp"
+#include "abaddon.hpp"
+#include "util.hpp"
#include "lazyimage.hpp"
#include <unordered_map>
@@ -231,11 +231,13 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
}
} break;
case MessageType::RECIPIENT_ADD: {
+ if (data->Mentions.size() == 0) break;
const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
const auto &added = data->Mentions[0];
b->insert_markup(s, "<i><span color='#999999'><span color='#eeeeee'>" + adder->Username + "</span> added <span color='#eeeeee'>" + added.Username + "</span></span></i>");
} break;
case MessageType::RECIPIENT_REMOVE: {
+ if (data->Mentions.size() == 0) break;
const auto &adder = Abaddon::Get().GetDiscordClient().GetUser(data->Author.ID);
const auto &added = data->Mentions[0];
if (adder->ID == added.ID)
@@ -355,7 +357,7 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb
}
return false;
});
- static auto color = Abaddon::Get().GetSettings().GetLinkColor();
+ static auto color = Abaddon::Get().GetSettings().LinkColor;
title_label->override_color(Gdk::RGBA(color));
title_label->set_markup("<b>" + Glib::Markup::escape_text(*embed.Title) + "</b>");
}
@@ -653,6 +655,14 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
return author->GetEscapedBoldString<false>();
};
+ // if the message wasnt fetched from store it might have an un-fetched reference
+ std::optional<std::shared_ptr<Message>> referenced_message = data.ReferencedMessage;
+ if (data.MessageReference.has_value() && data.MessageReference->MessageID.has_value() && !referenced_message.has_value()) {
+ auto refd = discord.GetMessage(*data.MessageReference->MessageID);
+ if (refd.has_value())
+ referenced_message = std::make_shared<Message>(std::move(*refd));
+ }
+
if (data.Interaction.has_value()) {
const auto user = *discord.GetUser(data.Interaction->User.ID);
@@ -664,16 +674,16 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data)
} else {
lbl->set_markup(user.GetEscapedBoldString<false>());
}
- } else if (data.ReferencedMessage.has_value()) {
- if (data.ReferencedMessage.value().get() == nullptr) {
+ } else if (referenced_message.has_value()) {
+ if (referenced_message.value() == nullptr) {
lbl->set_markup("<i>deleted message</i>");
} else {
- const auto &referenced = *data.ReferencedMessage.value().get();
+ const auto &referenced = *referenced_message.value();
Glib::ustring text;
- if (referenced.Content == "") {
- if (referenced.Attachments.size() > 0) {
+ if (referenced.Content.empty()) {
+ if (!referenced.Attachments.empty()) {
text = "<i>attachment</i>";
- } else if (referenced.Embeds.size() > 0) {
+ } else if (!referenced.Embeds.empty()) {
text = "<i>embed</i>";
}
} else {
@@ -788,7 +798,6 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
int mstart, mend;
if (!match.fetch_pos(0, mstart, mend)) break;
const bool is_animated = match.fetch(0)[1] == 'a';
- const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
const auto chars_end = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mend);
@@ -796,7 +805,7 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
auto end_it = buf->get_iter_at_offset(chars_end);
startpos = mend;
- if (is_animated && show_animations) {
+ if (is_animated && Abaddon::Get().GetSettings().ShowAnimations) {
const auto mark_start = buf->create_mark(start_it, false);
end_it.backward_char();
const auto mark_end = buf->create_mark(end_it, false);
@@ -825,7 +834,9 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
buf->delete_mark(mark_start);
buf->delete_mark(mark_end);
auto it = buf->erase(start_it, end_it);
- buf->insert_pixbuf(it, pixbuf->scale_simple(EmojiSize, EmojiSize, Gdk::INTERP_BILINEAR));
+ int width, height;
+ GetImageDimensions(pixbuf->get_width(), pixbuf->get_height(), width, height, EmojiSize, EmojiSize);
+ buf->insert_pixbuf(it, pixbuf->scale_simple(width, height, Gdk::INTERP_BILINEAR));
};
img.LoadFromURL(EmojiData::URLFromID(match.fetch(2)), sigc::track_obj(cb, tv));
}
@@ -835,11 +846,8 @@ void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView &tv) {
}
void ChatMessageItemContainer::HandleEmojis(Gtk::TextView &tv) {
- static const bool stock_emojis = Abaddon::Get().GetSettings().GetShowStockEmojis();
- static const bool custom_emojis = Abaddon::Get().GetSettings().GetShowCustomEmojis();
-
- if (stock_emojis) HandleStockEmojis(tv);
- if (custom_emojis) HandleCustomEmojis(tv);
+ if (Abaddon::Get().GetSettings().ShowStockEmojis) HandleStockEmojis(tv);
+ if (Abaddon::Get().GetSettings().ShowCustomEmojis) HandleCustomEmojis(tv);
}
void ChatMessageItemContainer::CleanupEmojis(Glib::RefPtr<Gtk::TextBuffer> buf) {
@@ -959,9 +967,6 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) {
auto buf = tv.get_buffer();
Glib::ustring text = GetText(buf);
- // i'd like to let this be done thru css like .message-link { color: #bitch; } but idk how
- static auto link_color = Abaddon::Get().GetSettings().GetLinkColor();
-
int startpos = 0;
Glib::MatchInfo match;
while (rgx->match(text, startpos, match)) {
@@ -970,7 +975,7 @@ void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) {
std::string link = match.fetch(0);
auto tag = buf->create_tag();
m_link_tagmap[tag] = link;
- tag->property_foreground_rgba() = Gdk::RGBA(link_color);
+ tag->property_foreground_rgba() = Gdk::RGBA(Abaddon::Get().GetSettings().LinkColor);
tag->set_property("underline", 1); // stupid workaround for vcpkg bug (i think)
const auto chars_start = g_utf8_pointer_to_offset(text.c_str(), text.c_str() + mstart);
@@ -1128,7 +1133,7 @@ ChatMessageHeader::ChatMessageHeader(const Message &data)
m_content_box_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_meta_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
m_avatar_ev.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
- if (Abaddon::Get().GetSettings().GetShowAnimations()) {
+ if (Abaddon::Get().GetSettings().ShowAnimations) {
m_content_box_ev.signal_enter_notify_event().connect(on_enter_cb);
m_content_box_ev.signal_leave_notify_event().connect(on_leave_cb);
m_meta_ev.signal_enter_notify_event().connect(on_enter_cb);
diff --git a/components/chatmessage.hpp b/src/components/chatmessage.hpp
index f319449..8b69117 100644
--- a/components/chatmessage.hpp
+++ b/src/components/chatmessage.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/discord.hpp"
+#include "discord/discord.hpp"
class ChatMessageItemContainer : public Gtk::Box {
public:
diff --git a/components/chatwindow.cpp b/src/components/chatwindow.cpp
index c2bd688..9b34dfd 100644
--- a/components/chatwindow.cpp
+++ b/src/components/chatwindow.cpp
@@ -1,6 +1,6 @@
#include "chatwindow.hpp"
#include "chatmessage.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
#include "chatinputindicator.hpp"
#include "ratelimitindicator.hpp"
#include "chatinput.hpp"
diff --git a/components/chatwindow.hpp b/src/components/chatwindow.hpp
index 5ef8bad..de55b0a 100644
--- a/components/chatwindow.hpp
+++ b/src/components/chatwindow.hpp
@@ -2,7 +2,7 @@
#include <gtkmm.h>
#include <string>
#include <set>
-#include "../discord/discord.hpp"
+#include "discord/discord.hpp"
#include "completer.hpp"
class ChatMessageHeader;
diff --git a/components/completer.cpp b/src/components/completer.cpp
index 258acb8..327ef95 100644
--- a/components/completer.cpp
+++ b/src/components/completer.cpp
@@ -1,7 +1,7 @@
#include <unordered_set>
#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;
diff --git a/components/completer.hpp b/src/components/completer.hpp
index a669824..6bd8be9 100644
--- a/components/completer.hpp
+++ b/src/components/completer.hpp
@@ -2,7 +2,7 @@
#include <gtkmm.h>
#include <functional>
#include "lazyimage.hpp"
-#include "../discord/snowflake.hpp"
+#include "discord/snowflake.hpp"
constexpr static int CompleterImageSize = 24;
diff --git a/components/draglistbox.cpp b/src/components/draglistbox.cpp
index 492abc3..492abc3 100644
--- a/components/draglistbox.cpp
+++ b/src/components/draglistbox.cpp
diff --git a/components/draglistbox.hpp b/src/components/draglistbox.hpp
index 9f204be..9f204be 100644
--- a/components/draglistbox.hpp
+++ b/src/components/draglistbox.hpp
diff --git a/components/friendslist.cpp b/src/components/friendslist.cpp
index ec78b57..1331d19 100644
--- a/components/friendslist.cpp
+++ b/src/components/friendslist.cpp
@@ -1,5 +1,5 @@
#include "friendslist.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
#include "lazyimage.hpp"
using namespace std::string_literals;
@@ -257,8 +257,7 @@ FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData
auto &discord = Abaddon::Get().GetDiscordClient();
discord.signal_presence_update().connect(sigc::mem_fun(*this, &FriendsListFriendRow::OnPresenceUpdate));
- static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
- if (data.HasAnimatedAvatar() && show_animations) {
+ if (data.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) {
img->SetAnimated(true);
img->SetURL(data.GetAvatarURL("gif", "32"));
} else {
diff --git a/components/friendslist.hpp b/src/components/friendslist.hpp
index 0101db6..460ad32 100644
--- a/components/friendslist.hpp
+++ b/src/components/friendslist.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/objects.hpp"
+#include "discord/objects.hpp"
class FriendsListAddComponent : public Gtk::Box {
public:
diff --git a/components/lazyimage.cpp b/src/components/lazyimage.cpp
index 5574c6c..49bbdeb 100644
--- a/components/lazyimage.cpp
+++ b/src/components/lazyimage.cpp
@@ -1,5 +1,5 @@
#include "lazyimage.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
LazyImage::LazyImage(int w, int h, bool use_placeholder)
: m_width(w)
diff --git a/components/lazyimage.hpp b/src/components/lazyimage.hpp
index fae69df..fae69df 100644
--- a/components/lazyimage.hpp
+++ b/src/components/lazyimage.hpp
diff --git a/components/memberlist.cpp b/src/components/memberlist.cpp
index ffc210b..4f21700 100644
--- a/components/memberlist.cpp
+++ b/src/components/memberlist.cpp
@@ -1,6 +1,6 @@
#include "memberlist.hpp"
-#include "../abaddon.hpp"
-#include "../util.hpp"
+#include "abaddon.hpp"
+#include "util.hpp"
#include "lazyimage.hpp"
#include "statusindicator.hpp"
@@ -14,8 +14,7 @@ MemberListUserRow::MemberListUserRow(const std::optional<GuildData> &guild, cons
m_avatar = Gtk::manage(new LazyImage(16, 16));
m_status_indicator = Gtk::manage(new StatusIndicator(ID));
- static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
- if (crown && guild.has_value() && guild->OwnerID == data.ID) {
+ if (Abaddon::Get().GetSettings().ShowOwnerCrown && guild.has_value() && 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);
@@ -40,9 +39,8 @@ 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 (show_discriminator)
+ if (Abaddon::Get().GetSettings().ShowMemberListDiscriminators)
display += "#" + data.Discriminator;
if (guild.has_value()) {
if (const auto col_id = data.GetHoistedRole(guild->ID, true); col_id.IsValid()) {
diff --git a/components/memberlist.hpp b/src/components/memberlist.hpp
index 180e76d..60a25bc 100644
--- a/components/memberlist.hpp
+++ b/src/components/memberlist.hpp
@@ -3,7 +3,7 @@
#include <mutex>
#include <unordered_map>
#include <optional>
-#include "../discord/discord.hpp"
+#include "discord/discord.hpp"
class LazyImage;
class StatusIndicator;
diff --git a/components/ratelimitindicator.cpp b/src/components/ratelimitindicator.cpp
index fe187db..ac4ef4b 100644
--- a/components/ratelimitindicator.cpp
+++ b/src/components/ratelimitindicator.cpp
@@ -1,5 +1,5 @@
#include "ratelimitindicator.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
#include <filesystem>
RateLimitIndicator::RateLimitIndicator()
diff --git a/components/ratelimitindicator.hpp b/src/components/ratelimitindicator.hpp
index d6ada43..b4dbb69 100644
--- a/components/ratelimitindicator.hpp
+++ b/src/components/ratelimitindicator.hpp
@@ -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:
diff --git a/components/statusindicator.cpp b/src/components/statusindicator.cpp
index a0616c0..42eb170 100644
--- a/components/statusindicator.cpp
+++ b/src/components/statusindicator.cpp
@@ -1,5 +1,5 @@
#include "statusindicator.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
static const constexpr int Diameter = 8;
static const auto OnlineColor = Gdk::RGBA("#43B581");
diff --git a/components/statusindicator.hpp b/src/components/statusindicator.hpp
index 9949b0d..b2cf0bd 100644
--- a/components/statusindicator.hpp
+++ b/src/components/statusindicator.hpp
@@ -1,7 +1,7 @@
#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:
diff --git a/config.h.in b/src/config.h.in
index ab6583d..ab6583d 100644
--- a/config.h.in
+++ b/src/config.h.in
diff --git a/constants.hpp b/src/constants.hpp
index 6c6276f..6c6276f 100644
--- a/constants.hpp
+++ b/src/constants.hpp
diff --git a/dialogs/confirm.cpp b/src/dialogs/confirm.cpp
index 39d8971..39d8971 100644
--- a/dialogs/confirm.cpp
+++ b/src/dialogs/confirm.cpp
diff --git a/dialogs/confirm.hpp b/src/dialogs/confirm.hpp
index df1e185..df1e185 100644
--- a/dialogs/confirm.hpp
+++ b/src/dialogs/confirm.hpp
diff --git a/dialogs/editmessage.cpp b/src/dialogs/editmessage.cpp
index b4308a0..b4308a0 100644
--- a/dialogs/editmessage.cpp
+++ b/src/dialogs/editmessage.cpp
diff --git a/dialogs/editmessage.hpp b/src/dialogs/editmessage.hpp
index bf6307d..bf6307d 100644
--- a/dialogs/editmessage.hpp
+++ b/src/dialogs/editmessage.hpp
diff --git a/dialogs/friendpicker.cpp b/src/dialogs/friendpicker.cpp
index 420fa9b..476e5f6 100644
--- a/dialogs/friendpicker.cpp
+++ b/src/dialogs/friendpicker.cpp
@@ -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().GetShowAnimations()) {
+ if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
m_avatar.property_pixbuf_animation() = pb;
};
diff --git a/dialogs/friendpicker.hpp b/src/dialogs/friendpicker.hpp
index 8621999..81d02a3 100644
--- a/dialogs/friendpicker.hpp
+++ b/src/dialogs/friendpicker.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/snowflake.hpp"
+#include "discord/snowflake.hpp"
class FriendPickerDialog : public Gtk::Dialog {
public:
diff --git a/dialogs/joinguild.cpp b/src/dialogs/joinguild.cpp
index 3ff1bdd..14fab53 100644
--- a/dialogs/joinguild.cpp
+++ b/src/dialogs/joinguild.cpp
@@ -1,5 +1,5 @@
#include "joinguild.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
#include <nlohmann/json.hpp>
#include <regex>
diff --git a/dialogs/joinguild.hpp b/src/dialogs/joinguild.hpp
index 109d010..109d010 100644
--- a/dialogs/joinguild.hpp
+++ b/src/dialogs/joinguild.hpp
diff --git a/dialogs/setstatus.cpp b/src/dialogs/setstatus.cpp
index 7a3a038..7a3a038 100644
--- a/dialogs/setstatus.cpp
+++ b/src/dialogs/setstatus.cpp
diff --git a/dialogs/setstatus.hpp b/src/dialogs/setstatus.hpp
index 97db906..b06c182 100644
--- a/dialogs/setstatus.hpp
+++ b/src/dialogs/setstatus.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/objects.hpp"
+#include "discord/objects.hpp"
class SetStatusDialog : public Gtk::Dialog {
public:
diff --git a/dialogs/token.cpp b/src/dialogs/token.cpp
index f984990..f984990 100644
--- a/dialogs/token.cpp
+++ b/src/dialogs/token.cpp
diff --git a/dialogs/token.hpp b/src/dialogs/token.hpp
index 7778bfb..7778bfb 100644
--- a/dialogs/token.hpp
+++ b/src/dialogs/token.hpp
diff --git a/dialogs/verificationgate.cpp b/src/dialogs/verificationgate.cpp
index 69bd2e2..698ddff 100644
--- a/dialogs/verificationgate.cpp
+++ b/src/dialogs/verificationgate.cpp
@@ -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)
diff --git a/dialogs/verificationgate.hpp b/src/dialogs/verificationgate.hpp
index 175006b..0a0dc08 100644
--- a/dialogs/verificationgate.hpp
+++ b/src/dialogs/verificationgate.hpp
@@ -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:
diff --git a/discord/activity.cpp b/src/discord/activity.cpp
index 95dda5d..95dda5d 100644
--- a/discord/activity.cpp
+++ b/src/discord/activity.cpp
diff --git a/discord/activity.hpp b/src/discord/activity.hpp
index 76ba9cd..6b8e944 100644
--- a/discord/activity.hpp
+++ b/src/discord/activity.hpp
@@ -1,7 +1,7 @@
#pragma once
#include <string>
#include <optional>
-#include "../util.hpp"
+#include "util.hpp"
#include "json.hpp"
#include "snowflake.hpp"
diff --git a/discord/auditlog.cpp b/src/discord/auditlog.cpp
index bfada39..bfada39 100644
--- a/discord/auditlog.cpp
+++ b/src/discord/auditlog.cpp
diff --git a/discord/auditlog.hpp b/src/discord/auditlog.hpp
index 3a902d1..3a902d1 100644
--- a/discord/auditlog.hpp
+++ b/src/discord/auditlog.hpp
diff --git a/discord/ban.cpp b/src/discord/ban.cpp
index a354c15..a354c15 100644
--- a/discord/ban.cpp
+++ b/src/discord/ban.cpp
diff --git a/discord/ban.hpp b/src/discord/ban.hpp
index d417ce3..d417ce3 100644
--- a/discord/ban.hpp
+++ b/src/discord/ban.hpp
diff --git a/discord/channel.cpp b/src/discord/channel.cpp
index 60f481b..80b1760 100644
--- a/discord/channel.cpp
+++ b/src/discord/channel.cpp
@@ -1,4 +1,4 @@
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
#include "channel.hpp"
void from_json(const nlohmann::json &j, ThreadMetadataData &m) {
diff --git a/discord/channel.hpp b/src/discord/channel.hpp
index 942d555..942d555 100644
--- a/discord/channel.hpp
+++ b/src/discord/channel.hpp
diff --git a/discord/discord.cpp b/src/discord/discord.cpp
index 671c29d..b678de0 100644
--- a/discord/discord.cpp
+++ b/src/discord/discord.cpp
@@ -1,8 +1,8 @@
#include "discord.hpp"
#include <cassert>
#include <cinttypes>
-#include "../util.hpp"
-#include "../abaddon.hpp"
+#include "util.hpp"
+#include "abaddon.hpp"
DiscordClient::DiscordClient(bool mem_store)
: m_decompress_buf(InflateChunkSize)
@@ -109,8 +109,8 @@ std::vector<Message> DiscordClient::GetMessagesForChannel(Snowflake id, size_t l
return m_store.GetLastMessages(id, limit);
}
-std::vector<Snowflake> DiscordClient::GetMessageIDsForChannel(Snowflake id) const {
- return m_store.GetChannelMessageIDs(id);
+std::vector<Message> DiscordClient::GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const {
+ return m_store.GetMessagesBefore(channel_id, message_id, limit);
}
void DiscordClient::FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback) {
@@ -292,6 +292,19 @@ void DiscordClient::GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<vo
});
}
+void DiscordClient::GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback) {
+ m_http.MakeGET("/channels/" + std::to_string(channel_id) + "/users/@me/threads/archived/private", [this, callback](const http::response_type &r) {
+ if (CheckCode(r)) {
+ const auto data = nlohmann::json::parse(r.text).get<ArchivedThreadsResponseData>();
+ for (const auto &thread : data.Threads)
+ m_store.SetChannel(thread.ID, thread);
+ callback(DiscordError::NONE, data);
+ } else {
+ callback(GetCodeFromResponse(r), {});
+ }
+ });
+}
+
bool DiscordClient::IsThreadJoined(Snowflake thread_id) const {
return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end();
}
@@ -707,7 +720,9 @@ void DiscordClient::ModifyRoleColor(Snowflake guild_id, Snowflake role_id, Gdk::
}
void DiscordClient::ModifyRolePosition(Snowflake guild_id, Snowflake role_id, int position, sigc::slot<void(DiscordError code)> callback) {
- const auto roles = GetGuild(guild_id)->FetchRoles();
+ const auto guild = GetGuild(guild_id);
+ if (!guild.has_value() || !guild->Roles.has_value()) return;
+ const auto &roles = *guild->Roles;
if (static_cast<size_t>(position) > roles.size()) return;
// gay and makes you send every role in between new and old position
constexpr auto IDX_MAX = ~size_t { 0 };
@@ -1303,13 +1318,11 @@ void DiscordClient::HandleGatewayHello(const GatewayMessage &msg) {
// perhaps this should be set by the main class
std::string DiscordClient::GetAPIURL() {
- static const auto url = Abaddon::Get().GetSettings().GetAPIBaseURL();
- return url;
+ return Abaddon::Get().GetSettings().APIBaseURL;
}
std::string DiscordClient::GetGatewayURL() {
- static const auto url = Abaddon::Get().GetSettings().GetGatewayURL();
- return url;
+ return Abaddon::Get().GetSettings().GatewayURL;
}
DiscordError DiscordClient::GetCodeFromResponse(const http::response_type &response) {
@@ -1349,7 +1362,7 @@ void DiscordClient::ProcessNewGuild(GuildData &guild) {
}
for (auto &r : *guild.Roles)
- m_store.SetRole(r.ID, r);
+ m_store.SetRole(guild.ID, r);
for (auto &e : *guild.Emojis)
m_store.SetEmoji(e.ID, e);
@@ -1517,7 +1530,7 @@ void DiscordClient::HandleGatewayGuildUpdate(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayGuildRoleUpdate(const GatewayMessage &msg) {
GuildRoleUpdateObject data = msg.Data;
- m_store.SetRole(data.Role.ID, data.Role);
+ m_store.SetRole(data.GuildID, data.Role);
m_signal_role_update.emit(data.GuildID, data.Role.ID);
}
@@ -1526,7 +1539,7 @@ void DiscordClient::HandleGatewayGuildRoleCreate(const GatewayMessage &msg) {
auto guild = *m_store.GetGuild(data.GuildID);
guild.Roles->push_back(data.Role);
m_store.BeginTransaction();
- m_store.SetRole(data.Role.ID, data.Role);
+ m_store.SetRole(guild.ID, data.Role);
m_store.SetGuild(guild.ID, guild);
m_store.EndTransaction();
m_signal_role_create.emit(data.GuildID, data.Role.ID);
@@ -1545,81 +1558,22 @@ void DiscordClient::HandleGatewayGuildRoleDelete(const GatewayMessage &msg) {
void DiscordClient::HandleGatewayMessageReactionAdd(const GatewayMessage &msg) {
MessageReactionAddObject data = msg.Data;
- auto to = m_store.GetMessage(data.MessageID);
- if (data.Emoji.ID.IsValid()) {
- const auto cur_emoji = m_store.GetEmoji(data.Emoji.ID);
- if (!cur_emoji.has_value())
- m_store.SetEmoji(data.Emoji.ID, data.Emoji);
- }
- if (!to.has_value()) return;
- if (!to->Reactions.has_value()) to->Reactions.emplace();
- // add if present
- bool stock;
- auto it = std::find_if(to->Reactions->begin(), to->Reactions->end(), [&](const ReactionData &x) {
- if (data.Emoji.ID.IsValid() && x.Emoji.ID.IsValid()) {
- stock = false;
- return data.Emoji.ID == x.Emoji.ID;
- } else {
- stock = true;
- return data.Emoji.Name == x.Emoji.Name;
- }
- });
-
- if (it != to->Reactions->end()) {
- it->Count++;
- if (data.UserID == GetUserData().ID)
- it->HasReactedWith = true;
- m_store.SetMessage(data.MessageID, *to);
- if (stock)
- m_signal_reaction_add.emit(data.MessageID, data.Emoji.Name);
- else
- m_signal_reaction_add.emit(data.MessageID, std::to_string(data.Emoji.ID));
- return;
- }
- // create new
- auto &rdata = to->Reactions->emplace_back();
- rdata.Count = 1;
- rdata.Emoji = data.Emoji;
- rdata.HasReactedWith = data.UserID == GetUserData().ID;
- m_store.SetMessage(data.MessageID, *to);
- if (stock)
- m_signal_reaction_add.emit(data.MessageID, data.Emoji.Name);
- else
+ m_store.AddReaction(data, data.UserID == GetUserData().ID);
+ if (data.Emoji.ID.IsValid())
m_signal_reaction_add.emit(data.MessageID, std::to_string(data.Emoji.ID));
+ else
+ m_signal_reaction_add.emit(data.MessageID, data.Emoji.Name);
}
void DiscordClient::HandleGatewayMessageReactionRemove(const GatewayMessage &msg) {
MessageReactionRemoveObject data = msg.Data;
- auto to = m_store.GetMessage(data.MessageID);
- if (!to.has_value()) return;
- if (!to->Reactions.has_value()) return;
- bool stock;
- auto it = std::find_if(to->Reactions->begin(), to->Reactions->end(), [&](const ReactionData &x) {
- if (data.Emoji.ID.IsValid() && x.Emoji.ID.IsValid()) {
- stock = false;
- return data.Emoji.ID == x.Emoji.ID;
- } else {
- stock = true;
- return data.Emoji.Name == x.Emoji.Name;
- }
- });
- if (it == to->Reactions->end()) return;
- if (it->Count == 1)
- to->Reactions->erase(it);
- else {
- if (data.UserID == GetUserData().ID)
- it->HasReactedWith = false;
- it->Count--;
- }
-
- m_store.SetMessage(data.MessageID, *to);
-
- if (stock)
- m_signal_reaction_remove.emit(data.MessageID, data.Emoji.Name);
- else
+ m_store.RemoveReaction(data, data.UserID == GetUserData().ID);
+ if (data.Emoji.ID.IsValid())
m_signal_reaction_remove.emit(data.MessageID, std::to_string(data.Emoji.ID));
+ else
+ m_signal_reaction_remove.emit(data.MessageID, data.Emoji.Name);
}
// todo: update channel list item and member list
@@ -1635,10 +1589,7 @@ void DiscordClient::HandleGatewayChannelRecipientAdd(const GatewayMessage &msg)
void DiscordClient::HandleGatewayChannelRecipientRemove(const GatewayMessage &msg) {
ChannelRecipientRemove data = msg.Data;
- auto cur = m_store.GetChannel(data.ChannelID);
- if (!cur.has_value() || !cur->RecipientIDs.has_value()) return;
- cur->RecipientIDs->erase(std::remove(cur->RecipientIDs->begin(), cur->RecipientIDs->end(), data.User.ID));
- m_store.SetChannel(cur->ID, *cur);
+ m_store.ClearRecipient(data.ChannelID, data.User.ID);
}
void DiscordClient::HandleGatewayTypingStart(const GatewayMessage &msg) {
@@ -2054,7 +2005,7 @@ void DiscordClient::SendIdentify() {
msg.Properties.ReferrerCurrent = "";
msg.Properties.ReferringDomainCurrent = "";
msg.Properties.ReleaseChannel = "stable";
- msg.Properties.ClientBuildNumber = 91734;
+ msg.Properties.ClientBuildNumber = 105691;
msg.Properties.ClientEventSource = "";
msg.Presence.Status = "online";
msg.Presence.Since = 0;
@@ -2063,7 +2014,10 @@ void DiscordClient::SendIdentify() {
msg.ClientState.HighestLastMessageID = "0";
msg.ClientState.ReadStateVersion = 0;
msg.ClientState.UserGuildSettingsVersion = -1;
+ const bool b = m_websocket.GetPrintMessages();
+ m_websocket.SetPrintMessages(false);
m_websocket.Send(msg);
+ m_websocket.SetPrintMessages(b);
}
void DiscordClient::SendResume() {
diff --git a/discord/discord.hpp b/src/discord/discord.hpp
index aefe1f6..4010977 100644
--- a/discord/discord.hpp
+++ b/src/discord/discord.hpp
@@ -53,20 +53,12 @@ public:
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::vector<Message> GetMessagesForChannel(Snowflake id, size_t limit = 50) const;
- std::vector<Snowflake> GetMessageIDsForChannel(Snowflake id) const;
+ std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit = 50) const;
std::set<Snowflake> GetPrivateChannels() const;
EPremiumType GetSelfPremiumType() const;
@@ -89,6 +81,7 @@ public:
std::vector<Snowflake> GetUsersInThread(Snowflake id) const;
std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const;
void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
+ void GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot<void(DiscordError, const ArchivedThreadsResponseData &)> callback);
bool IsThreadJoined(Snowflake thread_id) const;
bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const;
diff --git a/discord/emoji.cpp b/src/discord/emoji.cpp
index 1a97eb8..1a97eb8 100644
--- a/discord/emoji.cpp
+++ b/src/discord/emoji.cpp
diff --git a/discord/emoji.hpp b/src/discord/emoji.hpp
index 156e127..156e127 100644
--- a/discord/emoji.hpp
+++ b/src/discord/emoji.hpp
diff --git a/discord/errors.hpp b/src/discord/errors.hpp
index 4579563..4579563 100644
--- a/discord/errors.hpp
+++ b/src/discord/errors.hpp
diff --git a/discord/guild.cpp b/src/discord/guild.cpp
index 23a45ee..966bd44 100644
--- a/discord/guild.cpp
+++ b/src/discord/guild.cpp
@@ -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);
@@ -188,18 +188,6 @@ std::vector<Snowflake> GuildData::GetSortedChannels(Snowflake ignore) const {
return ret;
}
-std::vector<RoleData> GuildData::FetchRoles() const {
- if (!Roles.has_value()) return {};
- std::vector<RoleData> ret;
- ret.reserve(Roles->size());
- 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);
diff --git a/discord/guild.hpp b/src/discord/guild.hpp
index 3c3828d..51b5a01 100644
--- a/discord/guild.hpp
+++ b/src/discord/guild.hpp
@@ -50,7 +50,7 @@ struct GuildData {
std::optional<int> VerificationLevel;
std::optional<int> DefaultMessageNotifications;
std::optional<int> ExplicitContentFilter;
- std::optional<std::vector<RoleData>> Roles; // only access id
+ std::optional<std::vector<RoleData>> Roles;
std::optional<std::vector<EmojiData>> Emojis; // only access id
std::optional<std::unordered_set<std::string>> Features;
std::optional<int> MFALevel;
@@ -96,5 +96,4 @@ struct GuildData {
bool HasAnimatedIcon() 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
};
diff --git a/discord/httpclient.cpp b/src/discord/httpclient.cpp
index 05474df..05474df 100644
--- a/discord/httpclient.cpp
+++ b/src/discord/httpclient.cpp
diff --git a/discord/httpclient.hpp b/src/discord/httpclient.hpp
index 81723b8..da8be37 100644
--- a/discord/httpclient.hpp
+++ b/src/discord/httpclient.hpp
@@ -7,7 +7,7 @@
#include <mutex>
#include <queue>
#include <glibmm.h>
-#include "../http.hpp"
+#include "http.hpp"
class HTTPClient {
public:
diff --git a/discord/interactions.cpp b/src/discord/interactions.cpp
index 0bb09b1..cc439fc 100644
--- a/discord/interactions.cpp
+++ b/src/discord/interactions.cpp
@@ -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);
diff --git a/discord/interactions.hpp b/src/discord/interactions.hpp
index c076145..c076145 100644
--- a/discord/interactions.hpp
+++ b/src/discord/interactions.hpp
diff --git a/discord/invite.cpp b/src/discord/invite.cpp
index 63043a1..63043a1 100644
--- a/discord/invite.cpp
+++ b/src/discord/invite.cpp
diff --git a/discord/invite.hpp b/src/discord/invite.hpp
index c4c2cf3..c4c2cf3 100644
--- a/discord/invite.hpp
+++ b/src/discord/invite.hpp
diff --git a/discord/json.hpp b/src/discord/json.hpp
index de779b9..837080b 100644
--- a/discord/json.hpp
+++ b/src/discord/json.hpp
@@ -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>
diff --git a/discord/member.cpp b/src/discord/member.cpp
index 70a5727..29c4fae 100644
--- a/discord/member.cpp
+++ b/src/discord/member.cpp
@@ -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);
diff --git a/discord/member.hpp b/src/discord/member.hpp
index e17da05..e17da05 100644
--- a/discord/member.hpp
+++ b/src/discord/member.hpp
diff --git a/discord/message.cpp b/src/discord/message.cpp
index 70c557d..70c557d 100644
--- a/discord/message.cpp
+++ b/src/discord/message.cpp
diff --git a/discord/message.hpp b/src/discord/message.hpp
index 56f4c0f..56f4c0f 100644
--- a/discord/message.hpp
+++ b/src/discord/message.hpp
diff --git a/discord/objects.cpp b/src/discord/objects.cpp
index c6de2ce..c6de2ce 100644
--- a/discord/objects.cpp
+++ b/src/discord/objects.cpp
diff --git a/discord/objects.hpp b/src/discord/objects.hpp
index 7084efb..7084efb 100644
--- a/discord/objects.hpp
+++ b/src/discord/objects.hpp
diff --git a/discord/permissions.cpp b/src/discord/permissions.cpp
index 63eeb9f..63eeb9f 100644
--- a/discord/permissions.cpp
+++ b/src/discord/permissions.cpp
diff --git a/discord/permissions.hpp b/src/discord/permissions.hpp
index 5609135..56ef742 100644
--- a/discord/permissions.hpp
+++ b/src/discord/permissions.hpp
@@ -2,9 +2,9 @@
#include <cstdint>
#include "snowflake.hpp"
#include "json.hpp"
-#include "../util.hpp"
+#include "util.hpp"
-constexpr static uint64_t PERMISSION_MAX_BIT = 31;
+constexpr static uint64_t PERMISSION_MAX_BIT = 36;
enum class Permission : uint64_t {
NONE = 0,
CREATE_INSTANT_INVITE = (1ULL << 0), // Allows creation of instant invites
diff --git a/discord/relationship.cpp b/src/discord/relationship.cpp
index d65d2c1..d65d2c1 100644
--- a/discord/relationship.cpp
+++ b/src/discord/relationship.cpp
diff --git a/discord/relationship.hpp b/src/discord/relationship.hpp
index d492bd3..d492bd3 100644
--- a/discord/relationship.hpp
+++ b/src/discord/relationship.hpp
diff --git a/discord/role.cpp b/src/discord/role.cpp
index 07a912e..07a912e 100644
--- a/discord/role.cpp
+++ b/src/discord/role.cpp
diff --git a/discord/role.hpp b/src/discord/role.hpp
index f638b65..f638b65 100644
--- a/discord/role.hpp
+++ b/src/discord/role.hpp
diff --git a/discord/snowflake.cpp b/src/discord/snowflake.cpp
index 6909a15..6909a15 100644
--- a/discord/snowflake.cpp
+++ b/src/discord/snowflake.cpp
diff --git a/discord/snowflake.hpp b/src/discord/snowflake.hpp
index 1cabf3d..1cabf3d 100644
--- a/discord/snowflake.hpp
+++ b/src/discord/snowflake.hpp
diff --git a/discord/sticker.cpp b/src/discord/sticker.cpp
index b92d031..b92d031 100644
--- a/discord/sticker.cpp
+++ b/src/discord/sticker.cpp
diff --git a/discord/sticker.hpp b/src/discord/sticker.hpp
index d23fe7b..d23fe7b 100644
--- a/discord/sticker.hpp
+++ b/src/discord/sticker.hpp
diff --git a/src/discord/store.cpp b/src/discord/store.cpp
new file mode 100644
index 0000000..1cb7231
--- /dev/null
+++ b/src/discord/store.cpp
@@ -0,0 +1,2255 @@
+#include "store.hpp"
+#include <cinttypes>
+
+using namespace std::literals::string_literals;
+
+// hopefully the casting between signed and unsigned int64 doesnt cause issues
+
+Store::Store(bool mem_store)
+ : m_db_path(mem_store ? ":memory:" : std::filesystem::temp_directory_path() / "abaddon-store.db")
+ , m_db(m_db_path.string().c_str()) {
+ if (!m_db.OK()) {
+ fprintf(stderr, "error opening database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ m_db.Execute(R"(
+ PRAGMA writable_schema = 1;
+ DELETE FROM sqlite_master;
+ PRAGMA writable_schema = 0;
+ VACUUM;
+ PRAGMA integrity_check;
+ )");
+ if (!m_db.OK()) {
+ fprintf(stderr, "failed to clear database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db.Execute("PRAGMA journal_mode = WAL") != SQLITE_OK) {
+ fprintf(stderr, "enabling write-ahead-log failed: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db.Execute("PRAGMA synchronous = NORMAL") != SQLITE_OK) {
+ fprintf(stderr, "setting synchronous failed: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ m_ok &= CreateTables();
+ m_ok &= CreateStatements();
+}
+
+Store::~Store() {
+ m_db.Close();
+ if (!m_db.OK()) {
+ fprintf(stderr, "error closing database: %s\n", m_db.ErrStr());
+ return;
+ }
+
+ if (m_db_path != ":memory:") {
+ std::error_code ec;
+ std::filesystem::remove(m_db_path, ec);
+ }
+}
+
+bool Store::IsValid() const {
+ return m_db.OK() && m_ok;
+}
+
+void Store::SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban) {
+ auto &s = m_stmt_set_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ s->Bind(3, ban.Reason);
+
+ if (!s->Insert())
+ fprintf(stderr, "ban insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild_id), static_cast<uint64_t>(user_id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetChannel(Snowflake id, const ChannelData &chan) {
+ auto &s = m_stmt_set_chan;
+
+ s->Bind(1, id);
+ s->Bind(2, chan.Type);
+ s->Bind(3, chan.GuildID);
+ s->Bind(4, chan.Position);
+ s->Bind(5, chan.Name);
+ s->Bind(6, chan.Topic);
+ s->Bind(7, chan.IsNSFW);
+ s->Bind(8, chan.LastMessageID);
+ s->Bind(9, chan.Bitrate);
+ s->Bind(10, chan.UserLimit);
+ s->Bind(11, chan.RateLimitPerUser);
+ s->Bind(12, chan.Icon);
+ s->Bind(13, chan.OwnerID);
+ s->Bind(14, chan.ApplicationID);
+ s->Bind(15, chan.ParentID);
+ s->Bind(16, chan.LastPinTimestamp);
+ if (chan.ThreadMetadata.has_value()) {
+ s->Bind(17, chan.ThreadMetadata->IsArchived);
+ s->Bind(18, chan.ThreadMetadata->AutoArchiveDuration);
+ s->Bind(19, chan.ThreadMetadata->ArchiveTimestamp);
+ } else {
+ s->Bind(17);
+ s->Bind(18);
+ s->Bind(19);
+ }
+
+ if (!s->Insert())
+ fprintf(stderr, "channel insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ if (chan.Recipients.has_value()) {
+ BeginTransaction();
+ auto &s = m_stmt_set_recipient;
+ for (const auto &r : *chan.Recipients) {
+ s->Bind(1, chan.ID);
+ s->Bind(2, r.ID);
+ if (!s->Insert())
+ fprintf(stderr, "recipient insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(chan.ID), static_cast<uint64_t>(r.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ } else if (chan.RecipientIDs.has_value()) {
+ BeginTransaction();
+ auto &s = m_stmt_set_recipient;
+ for (const auto &id : *chan.RecipientIDs) {
+ s->Bind(1, chan.ID);
+ s->Bind(2, id);
+ if (!s->Insert())
+ fprintf(stderr, "recipient insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(chan.ID), static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ }
+
+ s->Reset();
+}
+
+void Store::SetEmoji(Snowflake id, const EmojiData &emoji) {
+ auto &s = m_stmt_set_emoji;
+
+ s->Bind(1, id);
+ s->Bind(2, emoji.Name);
+ if (emoji.Creator.has_value())
+ s->Bind(3, emoji.Creator->ID);
+ else
+ s->Bind(3);
+ s->Bind(4, emoji.NeedsColons);
+ s->Bind(5, emoji.IsManaged);
+ s->Bind(6, emoji.IsAnimated);
+ s->Bind(7, emoji.IsAvailable);
+
+ if (emoji.Roles.has_value()) {
+ BeginTransaction();
+
+ auto &s = m_stmt_set_emoji_role;
+
+ for (const auto &r : *emoji.Roles) {
+ s->Bind(1, id);
+ s->Bind(2, r);
+ if (!s->Insert())
+ fprintf(stderr, "emoji role insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(r), m_db.ErrStr());
+ s->Reset();
+ }
+
+ EndTransaction();
+ }
+
+ if (!s->Insert())
+ fprintf(stderr, "emoji insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetGuild(Snowflake id, const GuildData &guild) {
+ BeginTransaction();
+ auto &s = m_stmt_set_guild;
+
+ s->Bind(1, guild.ID);
+ s->Bind(2, guild.Name);
+ s->Bind(3, guild.Icon);
+ s->Bind(4, guild.Splash);
+ s->Bind(5, guild.IsOwner);
+ s->Bind(6, guild.OwnerID);
+ s->Bind(7, guild.PermissionsNew);
+ s->Bind(8, guild.VoiceRegion);
+ s->Bind(9, guild.AFKChannelID);
+ s->Bind(10, guild.AFKTimeout);
+ s->Bind(11, guild.VerificationLevel);
+ s->Bind(12, guild.DefaultMessageNotifications);
+ s->Bind(13, guild.MFALevel);
+ s->Bind(14, guild.ApplicationID);
+ s->Bind(15, guild.IsWidgetEnabled);
+ s->Bind(16, guild.WidgetChannelID);
+ s->Bind(17, guild.SystemChannelFlags);
+ s->Bind(18, guild.RulesChannelID);
+ s->Bind(19, guild.JoinedAt);
+ s->Bind(20, guild.IsLarge);
+ s->Bind(21, guild.IsUnavailable);
+ s->Bind(22, guild.MemberCount);
+ s->Bind(23, guild.MaxPresences);
+ s->Bind(24, guild.MaxMembers);
+ s->Bind(25, guild.VanityURL);
+ s->Bind(26, guild.Description);
+ s->Bind(27, guild.BannerHash);
+ s->Bind(28, guild.PremiumTier);
+ s->Bind(29, guild.PremiumSubscriptionCount);
+ s->Bind(30, guild.PreferredLocale);
+ s->Bind(31, guild.PublicUpdatesChannelID);
+ s->Bind(32, guild.MaxVideoChannelUsers);
+ s->Bind(33, guild.ApproximateMemberCount);
+ s->Bind(34, guild.ApproximatePresenceCount);
+ s->Bind(35, guild.IsLazy);
+
+ if (!s->Insert())
+ fprintf(stderr, "guild insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), m_db.ErrStr());
+
+ s->Reset();
+
+ if (guild.Emojis.has_value()) {
+ auto &s = m_stmt_set_guild_emoji;
+ for (const auto &emoji : *guild.Emojis) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, emoji.ID);
+ if (!s->Insert())
+ fprintf(stderr, "guild emoji insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), static_cast<uint64_t>(emoji.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (guild.Features.has_value()) {
+ auto &s = m_stmt_set_guild_feature;
+
+ for (const auto &feature : *guild.Features) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, feature);
+ if (!s->Insert())
+ fprintf(stderr, "guild feature insert failed for %" PRIu64 "/%s: %s\n", static_cast<uint64_t>(guild.ID), feature.c_str(), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (guild.Threads.has_value()) {
+ auto &s = m_stmt_set_thread;
+
+ for (const auto &thread : *guild.Threads) {
+ s->Bind(1, guild.ID);
+ s->Bind(2, thread.ID);
+ if (!s->Insert())
+ fprintf(stderr, "guild thread insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild.ID), static_cast<uint64_t>(thread.ID), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ EndTransaction();
+}
+
+void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data) {
+ auto &s = m_stmt_set_member;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+ s->Bind(3, data.Nickname);
+ s->Bind(4, data.JoinedAt);
+ s->Bind(5, data.PremiumSince);
+ s->Bind(6, data.IsDeafened);
+ s->Bind(7, data.IsMuted);
+ s->Bind(8, data.Avatar);
+ s->Bind(9, data.IsPending);
+
+ if (!s->Insert())
+ fprintf(stderr, "member insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), m_db.ErrStr());
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_set_member_roles;
+
+ BeginTransaction();
+ for (const auto &role : data.Roles) {
+ s->Bind(1, user_id);
+ s->Bind(2, role);
+ if (!s->Insert())
+ fprintf(stderr, "member role insert failed for %" PRIu64 "/%" PRIu64 "/%" PRIu64 ": %s\n",
+ static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), static_cast<uint64_t>(role), m_db.ErrStr());
+ s->Reset();
+ }
+ EndTransaction();
+ }
+}
+
+void Store::SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction) {
+ auto &s = m_stmt_set_interaction;
+
+ s->Bind(1, message_id);
+ s->Bind(2, interaction.ID);
+ s->Bind(3, interaction.Type);
+ s->Bind(4, interaction.Name);
+ s->Bind(5, interaction.User.ID);
+
+ if (!s->Insert())
+ fprintf(stderr, "message interaction failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(message_id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetMessage(Snowflake id, const Message &message) {
+ auto &s = m_stmt_set_msg;
+
+ BeginTransaction();
+
+ s->Bind(1, id);
+ s->Bind(2, message.ChannelID);
+ s->Bind(3, message.GuildID);
+ s->Bind(4, message.Author.ID);
+ s->Bind(5, message.Content);
+ s->Bind(6, message.Timestamp);
+ s->Bind(7, message.EditedTimestamp);
+ s->Bind(8, message.IsTTS);
+ s->Bind(9, message.DoesMentionEveryone);
+ s->BindAsJSON(10, message.Embeds);
+ s->Bind(11, message.IsPinned);
+ s->Bind(12, message.WebhookID);
+ s->Bind(13, message.Type);
+ s->BindAsJSON(14, message.Application);
+ s->Bind(15, message.Flags);
+ s->BindAsJSON(16, message.Stickers);
+ s->Bind(17, message.IsDeleted());
+ s->Bind(18, message.IsEdited());
+ s->Bind(19, message.IsPending);
+ s->Bind(20, message.Nonce);
+ s->BindAsJSON(21, message.StickerItems);
+
+ if (!s->Insert())
+ fprintf(stderr, "message insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+
+ if (message.MessageReference.has_value()) {
+ auto &s = m_stmt_set_msg_ref;
+ s->Bind(1, message.ID);
+ s->Bind(2, message.MessageReference->MessageID);
+ s->Bind(3, message.MessageReference->ChannelID);
+ s->Bind(4, message.MessageReference->GuildID);
+
+ if (!s->Insert())
+ fprintf(stderr, "message ref insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+ }
+
+ for (const auto &u : message.Mentions) {
+ auto &s = m_stmt_set_mention;
+ s->Bind(1, id);
+ s->Bind(2, u.ID);
+ if (!s->Insert())
+ fprintf(stderr, "message mention insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(u.ID), m_db.ErrStr());
+ s->Reset();
+ }
+
+ for (const auto &a : message.Attachments) {
+ auto &s = m_stmt_set_attachment;
+ s->Bind(1, id);
+ s->Bind(2, a.ID);
+ s->Bind(3, a.Filename);
+ s->Bind(4, a.Bytes);
+ s->Bind(5, a.URL);
+ s->Bind(6, a.ProxyURL);
+ s->Bind(7, a.Height);
+ s->Bind(8, a.Width);
+ if (!s->Insert())
+ fprintf(stderr, "message attachment insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(a.ID), m_db.ErrStr());
+ s->Reset();
+ }
+
+ if (message.Reactions.has_value()) {
+ auto &s = m_stmt_add_reaction;
+ for (size_t i = 0; i < message.Reactions->size(); i++) {
+ const auto &reaction = (*message.Reactions)[i];
+ s->Bind(1, id);
+ s->Bind(2, reaction.Emoji.ID);
+ s->Bind(3, reaction.Emoji.Name);
+ s->Bind(4, reaction.Count);
+ s->Bind(5, reaction.HasReactedWith);
+ s->Bind(6, i);
+ if (!s->Insert())
+ fprintf(stderr, "message reaction insert failed for %" PRIu64 "/%" PRIu64 "/%s: %s\n", static_cast<uint64_t>(id), static_cast<uint64_t>(reaction.Emoji.ID), reaction.Emoji.Name.c_str(), m_db.ErrStr());
+ s->Reset();
+ }
+ }
+
+ if (message.Interaction.has_value())
+ SetMessageInteractionPair(id, *message.Interaction);
+
+ EndTransaction();
+}
+
+void Store::SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm) {
+ auto &s = m_stmt_set_perm;
+
+ s->Bind(1, perm.ID);
+ s->Bind(2, channel_id);
+ s->Bind(3, perm.Type);
+ s->Bind(4, perm.Allow);
+ s->Bind(5, perm.Deny);
+
+ if (!s->Insert())
+ fprintf(stderr, "permission insert failed for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(channel_id), static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetRole(Snowflake guild_id, const RoleData &role) {
+ auto &s = m_stmt_set_role;
+
+ s->Bind(1, role.ID);
+ s->Bind(2, guild_id);
+ s->Bind(3, role.Name);
+ s->Bind(4, role.Color);
+ s->Bind(5, role.IsHoisted);
+ s->Bind(6, role.Position);
+ s->Bind(7, role.Permissions);
+ s->Bind(8, role.IsManaged);
+ s->Bind(9, role.IsMentionable);
+
+ if (!s->Insert())
+ fprintf(stderr, "role insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(role.ID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::SetUser(Snowflake id, const UserData &user) {
+ auto &s = m_stmt_set_user;
+
+ s->Bind(1, id);
+ s->Bind(2, user.Username);
+ s->Bind(3, user.Discriminator);
+ s->Bind(4, user.Avatar);
+ s->Bind(5, user.IsBot);
+ s->Bind(6, user.IsSystem);
+ s->Bind(7, user.IsMFAEnabled);
+ s->Bind(8, user.PremiumType);
+ s->Bind(9, user.PublicFlags);
+
+ if (!s->Insert())
+ fprintf(stderr, "user insert failed for %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+
+ s->Reset();
+}
+
+std::optional<BanData> Store::GetBan(Snowflake guild_id, Snowflake user_id) const {
+ auto &s = m_stmt_get_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching ban for %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(guild_id), static_cast<uint64_t>(user_id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ BanData r;
+ r.User.ID = user_id;
+ s->Get(2, r.Reason);
+
+ s->Reset();
+
+ return r;
+}
+
+std::vector<BanData> Store::GetBans(Snowflake guild_id) const {
+ auto &s = m_stmt_get_bans;
+
+ std::vector<BanData> ret;
+ s->Bind(1, guild_id);
+ while (s->FetchOne()) {
+ auto &ban = ret.emplace_back();
+ s->Get(1, ban.User.ID);
+ s->Get(2, ban.Reason);
+ }
+
+ s->Reset();
+
+ return ret;
+}
+
+std::vector<Message> Store::GetLastMessages(Snowflake id, size_t num) const {
+ auto &s = m_stmt_get_last_msgs;
+ std::vector<Message> msgs;
+ s->Bind(1, id);
+ s->Bind(2, num);
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<Message> Store::GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const {
+ std::vector<Message> msgs;
+
+ auto &s = m_stmt_get_messages_before;
+
+ s->Bind(1, channel_id);
+ s->Bind(2, message_id);
+ s->Bind(3, limit);
+
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<Message> Store::GetPinnedMessages(Snowflake channel_id) const {
+ std::vector<Message> msgs;
+
+ auto &s = m_stmt_get_pins;
+
+ s->Bind(1, channel_id);
+
+ while (s->FetchOne()) {
+ auto msg = GetMessageBound(s);
+ msgs.push_back(std::move(msg));
+ }
+
+ s->Reset();
+
+ for (auto &msg : msgs) {
+ if (msg.MessageReference.has_value() && msg.MessageReference->MessageID.has_value()) {
+ auto ref = GetMessage(*msg.MessageReference->MessageID);
+ if (ref.has_value())
+ msg.ReferencedMessage = std::make_shared<Message>(std::move(*ref));
+ }
+ }
+
+ return msgs;
+}
+
+std::vector<ChannelData> Store::GetActiveThreads(Snowflake channel_id) const {
+ std::vector<ChannelData> ret;
+
+ auto &s = m_stmt_get_active_threads;
+
+ s->Bind(1, channel_id);
+ while (s->FetchOne()) {
+ Snowflake x;
+ s->Get(0, x);
+ auto chan = GetChannel(x);
+ if (chan.has_value())
+ ret.push_back(*chan);
+ }
+
+ s->Reset();
+
+ return ret;
+}
+
+void Store::AddReaction(const MessageReactionAddObject &data, bool byself) {
+ auto &s = m_stmt_add_reaction;
+
+ s->Bind(1, data.MessageID);
+ s->Bind(2, data.Emoji.ID);
+ s->Bind(3, data.Emoji.Name);
+ s->Bind(4, 1);
+ if (byself)
+ s->Bind(5, true);
+ else
+ s->Bind(5);
+ s->Bind(6);
+
+ if (!s->Insert())
+ fprintf(stderr, "failed to add reaction for %" PRIu64 ": %s\n", static_cast<uint64_t>(data.MessageID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+void Store::RemoveReaction(const MessageReactionRemoveObject &data, bool byself) {
+ auto &s = m_stmt_sub_reaction;
+
+ s->Bind(1, data.MessageID);
+ s->Bind(2, data.Emoji.ID);
+ s->Bind(3, data.Emoji.Name);
+ if (byself)
+ s->Bind(4, false);
+ else
+ s->Bind(4);
+
+ if (!s->Insert())
+ fprintf(stderr, "failed to remove reaction for %" PRIu64 ": %s\n", static_cast<uint64_t>(data.MessageID), m_db.ErrStr());
+
+ s->Reset();
+}
+
+std::optional<ChannelData> Store::GetChannel(Snowflake id) const {
+ auto &s = m_stmt_get_chan;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching channel %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ ChannelData r;
+
+ // uncomment as necessary
+ r.ID = id;
+ s->Get(1, r.Type);
+ s->Get(2, r.GuildID);
+ s->Get(3, r.Position);
+ s->Get(4, r.Name);
+ s->Get(5, r.Topic);
+ s->Get(6, r.IsNSFW);
+ s->Get(7, r.LastMessageID);
+ s->Get(10, r.RateLimitPerUser);
+ s->Get(12, r.OwnerID);
+ s->Get(14, r.ParentID);
+ if (!s->IsNull(16)) {
+ r.ThreadMetadata.emplace();
+ s->Get(16, r.ThreadMetadata->IsArchived);
+ s->Get(17, r.ThreadMetadata->AutoArchiveDuration);
+ s->Get(18, r.ThreadMetadata->ArchiveTimestamp);
+ }
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_recipients;
+ s->Bind(1, id);
+ std::vector<Snowflake> recipients;
+ while (s->FetchOne()) {
+ auto &r = recipients.emplace_back();
+ s->Get(0, r);
+ }
+ s->Reset();
+ if (!recipients.empty())
+ r.RecipientIDs = std::move(recipients);
+ }
+
+ return r;
+}
+
+std::optional<EmojiData> Store::GetEmoji(Snowflake id) const {
+ auto &s = m_stmt_get_emoji;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching emoji %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ EmojiData r;
+
+ r.ID = id;
+ s->Get(1, r.Name);
+ if (!s->IsNull(2)) {
+ r.Creator.emplace();
+ s->Get(2, r.Creator->ID);
+ }
+ s->Get(3, r.NeedsColons);
+ s->Get(4, r.IsManaged);
+ s->Get(5, r.IsAnimated);
+ s->Get(6, r.IsAvailable);
+
+ {
+ auto &s = m_stmt_get_emoji_roles;
+
+ s->Bind(1, id);
+ r.Roles.emplace();
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.Roles->push_back(id);
+ }
+ s->Reset();
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+std::optional<GuildData> Store::GetGuild(Snowflake id) const {
+ auto &s = m_stmt_get_guild;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching guild %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ // unfetched fields arent used anywhere
+ GuildData r;
+ r.ID = id;
+ s->Get(1, r.Name);
+ s->Get(2, r.Icon);
+ s->Get(5, r.OwnerID);
+ s->Get(20, r.IsUnavailable);
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_guild_emojis;
+
+ s->Bind(1, id);
+ r.Emojis.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Emojis->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_features;
+
+ s->Bind(1, id);
+ r.Features.emplace();
+ while (s->FetchOne()) {
+ std::string feature;
+ s->Get(0, feature);
+ r.Features->insert(feature);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_chans;
+ s->Bind(1, id);
+ r.Channels.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Channels->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_threads;
+ s->Bind(1, id);
+ r.Threads.emplace();
+ while (s->FetchOne()) {
+ auto &q = r.Threads->emplace_back();
+ s->Get(0, q.ID);
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_guild_roles;
+ s->Bind(1, id);
+ r.Roles.emplace();
+ while (s->FetchOne()) {
+ r.Roles->push_back(GetRoleBound(s));
+ }
+ s->Reset();
+ }
+
+ return r;
+}
+
+std::optional<GuildMember> Store::GetGuildMember(Snowflake guild_id, Snowflake user_id) const {
+ auto &s = m_stmt_get_member;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching member %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(user_id), static_cast<uint64_t>(guild_id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ GuildMember r;
+ r.User.emplace().ID = user_id;
+ s->Get(2, r.Nickname);
+ s->Get(3, r.JoinedAt);
+ s->Get(4, r.PremiumSince);
+ //s->Get(5, r.IsDeafened);
+ //s->Get(6, r.IsMuted);
+ s->Get(7, r.Avatar);
+ s->Get(8, r.IsPending);
+
+ s->Reset();
+
+ {
+ auto &s = m_stmt_get_member_roles;
+
+ s->Bind(1, user_id);
+ s->Bind(2, guild_id);
+
+ while (s->FetchOne()) {
+ auto &f = r.Roles.emplace_back();
+ s->Get(0, f);
+ }
+
+ s->Reset();
+ }
+
+ return r;
+}
+
+std::optional<Message> Store::GetMessage(Snowflake id) const {
+ auto &s = m_stmt_get_msg;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching message %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ auto top = GetMessageBound(s);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching message %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return top;
+ }
+
+ auto ref = GetMessageBound(s);
+ top.ReferencedMessage = std::make_shared<Message>(std::move(ref));
+
+ s->Reset();
+
+ return top;
+}
+
+Message Store::GetMessageBound(std::unique_ptr<Statement> &s) const {
+ Message r;
+
+ s->Get(0, r.ID);
+ s->Get(1, r.ChannelID);
+ s->Get(2, r.GuildID);
+ s->Get(3, r.Author.ID);
+ s->Get(4, r.Content);
+ s->Get(5, r.Timestamp);
+ s->Get(6, r.EditedTimestamp);
+ //s->Get(7, r.IsTTS);
+ //s->Get(8, r.DoesMentionEveryone);
+ s->GetJSON(9, r.Embeds);
+ s->Get(10, r.IsPinned);
+ s->Get(11, r.WebhookID);
+ s->Get(12, r.Type);
+ s->GetJSON(13, r.Application);
+ s->Get(14, r.Flags);
+ s->GetJSON(15, r.Stickers);
+ bool tmpb;
+ s->Get(16, tmpb);
+ if (tmpb) r.SetDeleted();
+ s->Get(17, tmpb);
+ if (tmpb) r.SetEdited();
+ s->Get(18, r.IsPending);
+ s->Get(19, r.Nonce);
+ s->GetJSON(20, r.StickerItems);
+
+ if (!s->IsNull(21)) {
+ auto &i = r.Interaction.emplace();
+ s->Get(21, i.ID);
+ s->Get(22, i.Name);
+ s->Get(23, i.Type);
+ s->Get(24, i.User.ID);
+ }
+
+ if (!s->IsNull(25)) {
+ auto &a = r.Attachments.emplace_back();
+ s->Get(25, a.ID);
+ s->Get(26, a.Filename);
+ s->Get(27, a.Bytes);
+ s->Get(28, a.URL);
+ s->Get(29, a.ProxyURL);
+ s->Get(30, a.Height);
+ s->Get(31, a.Width);
+ }
+
+ if (!s->IsNull(32)) {
+ auto &q = r.MessageReference.emplace();
+ s->Get(32, q.MessageID);
+ s->Get(33, q.ChannelID);
+ s->Get(34, q.GuildID);
+ }
+
+ {
+ auto &s = m_stmt_get_mentions;
+ s->Bind(1, r.ID);
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ auto user = GetUser(id);
+ if (user.has_value())
+ r.Mentions.push_back(std::move(*user));
+ }
+ s->Reset();
+ }
+
+ {
+ auto &s = m_stmt_get_reactions;
+ s->Bind(1, r.ID);
+ std::map<size_t, ReactionData> tmp;
+ while (s->FetchOne()) {
+ size_t idx;
+ ReactionData q;
+ s->Get(0, q.Emoji.ID);
+ s->Get(1, q.Emoji.Name);
+ s->Get(2, q.Count);
+ s->Get(3, q.HasReactedWith);
+ s->Get(4, idx);
+ tmp[idx] = q;
+ }
+ s->Reset();
+
+ r.Reactions.emplace();
+ for (const auto &[idx, reaction] : tmp)
+ r.Reactions->push_back(reaction);
+ }
+
+ return r;
+}
+
+std::optional<PermissionOverwrite> Store::GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const {
+ auto &s = m_stmt_get_perm;
+
+ s->Bind(1, id);
+ s->Bind(2, channel_id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "failed while fetching permission %" PRIu64 "/%" PRIu64 ": %s\n", static_cast<uint64_t>(channel_id), static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ PermissionOverwrite r;
+ r.ID = id;
+ s->Get(2, r.Type);
+ s->Get(3, r.Allow);
+ s->Get(4, r.Deny);
+
+ s->Reset();
+
+ return r;
+}
+
+std::optional<RoleData> Store::GetRole(Snowflake id) const {
+ auto &s = m_stmt_get_role;
+
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching role %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ auto role = GetRoleBound(s);
+
+ s->Reset();
+
+ return role;
+}
+
+RoleData Store::GetRoleBound(std::unique_ptr<Statement> &s) const {
+ RoleData r;
+
+ s->Get(0, r.ID);
+ //s->Get(1, guild id);
+ s->Get(2, r.Name);
+ s->Get(3, r.Color);
+ s->Get(4, r.IsHoisted);
+ s->Get(5, r.Position);
+ s->Get(6, r.Permissions);
+ s->Get(7, r.IsManaged);
+ s->Get(8, r.IsMentionable);
+
+ return r;
+}
+
+std::optional<UserData> Store::GetUser(Snowflake id) const {
+ auto &s = m_stmt_get_user;
+ s->Bind(1, id);
+ if (!s->FetchOne()) {
+ if (m_db.Error() != SQLITE_DONE)
+ fprintf(stderr, "error while fetching user %" PRIu64 ": %s\n", static_cast<uint64_t>(id), m_db.ErrStr());
+ s->Reset();
+ return {};
+ }
+
+ UserData r;
+
+ r.ID = id;
+ s->Get(1, r.Username);
+ s->Get(2, r.Discriminator);
+ s->Get(3, r.Avatar);
+ s->Get(4, r.IsBot);
+ s->Get(5, r.IsSystem);
+ s->Get(6, r.IsMFAEnabled);
+ s->Get(7, r.PremiumType);
+ s->Get(8, r.PublicFlags);
+
+ s->Reset();
+
+ return r;
+}
+
+void Store::ClearGuild(Snowflake id) {
+ auto &s = m_stmt_clr_guild;
+
+ s->Bind(1, id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearChannel(Snowflake id) {
+ auto &s = m_stmt_clr_chan;
+
+ s->Bind(1, id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearBan(Snowflake guild_id, Snowflake user_id) {
+ auto &s = m_stmt_clr_ban;
+
+ s->Bind(1, guild_id);
+ s->Bind(2, user_id);
+ s->Step();
+ s->Reset();
+}
+
+void Store::ClearRecipient(Snowflake channel_id, Snowflake user_id) {
+ auto &s = m_stmt_clr_recipient;
+
+ s->Bind(1, channel_id);
+ s->Bind(2, user_id);
+ s->Step();
+ s->Reset();
+}
+
+std::unordered_set<Snowflake> Store::GetChannels() const {
+ auto &s = m_stmt_get_chan_ids;
+ std::unordered_set<Snowflake> r;
+
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.insert(id);
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+std::unordered_set<Snowflake> Store::GetGuilds() const {
+ auto &s = m_stmt_get_guild_ids;
+ std::unordered_set<Snowflake> r;
+
+ while (s->FetchOne()) {
+ Snowflake id;
+ s->Get(0, id);
+ r.insert(id);
+ }
+
+ s->Reset();
+
+ return r;
+}
+
+void Store::ClearAll() {
+ if (m_db.Execute(R"(
+ DELETE FROM attachments;
+ DELETE FROM bans;
+ DELETE FROM channels;
+ DELETE FROM emojis;
+ DELETE FROM emoji_roles;
+ DELETE FROM guild_emojis;
+ DELETE FROM guild_features;
+ DELETE FROM guilds;
+ DELETE FROM members;
+ DELETE FROM member_roles;
+ DELETE FROM mentions;
+ DELETE FROM message_interactions;
+ DELETE FROM message_references;
+ DELETE FROM messages;
+ DELETE FROM permissions;
+ DELETE FROM reactions;
+ DELETE FROM recipients;
+ DELETE FROM roles;
+ DELETE FROM threads;
+ DELETE FROM users;
+ )") != SQLITE_OK) {
+ fprintf(stderr, "failed to clear: %s\n", m_db.ErrStr());
+ }
+}
+
+void Store::BeginTransaction() {
+ m_db.StartTransaction();
+}
+
+void Store::EndTransaction() {
+ m_db.EndTransaction();
+}
+
+bool Store::CreateTables() {
+ const char *create_users = R"(
+ CREATE TABLE IF NOT EXISTS users (
+ id INTEGER PRIMARY KEY,
+ username TEXT NOT NULL,
+ discriminator TEXT NOT NULL,
+ avatar TEXT,
+ bot BOOL,
+ system BOOL,
+ mfa BOOL,
+ premium INTEGER,
+ pubflags INTEGER
+ )
+ )";
+
+ const char *create_permissions = R"(
+ CREATE TABLE IF NOT EXISTS permissions (
+ id INTEGER NOT NULL,
+ channel_id INTEGER NOT NULL,
+ type INTEGER NOT NULL,
+ allow INTEGER NOT NULL,
+ deny INTEGER NOT NULL,
+ PRIMARY KEY(id, channel_id)
+ )
+ )";
+
+ const char *create_messages = R"(
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY,
+ channel_id INTEGER NOT NULL,
+ guild_id INTEGER,
+ author_id INTEGER NOT NULL,
+ content TEXT NOT NULL,
+ timestamp TEXT NOT NULL,
+ edited_timestamp TEXT,
+ tts BOOL NOT NULL,
+ everyone BOOL NOT NULL,
+ embeds TEXT NOT NULL, /* json */
+ pinned BOOL,
+ webhook_id INTEGER,
+ type INTEGER,
+ application TEXT, /* json */
+ flags INTEGER,
+ stickers TEXT, /* json */
+ deleted BOOL, /* extra */
+ edited BOOL, /* extra */
+ pending BOOL, /* extra */
+ nonce TEXT,
+ sticker_items TEXT /* json */
+ )
+ )";
+
+ const char *create_roles = R"(
+ CREATE TABLE IF NOT EXISTS roles (
+ id INTEGER PRIMARY KEY,
+ guild INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ color INTEGER NOT NULL,
+ hoisted BOOL NOT NULL,
+ position INTEGER NOT NULL,
+ permissions INTEGER NOT NULL,
+ managed BOOL NOT NULL,
+ mentionable BOOL NOT NULL
+ )
+ )";
+
+ const char *create_emojis = R"(
+ CREATE TABLE IF NOT EXISTS emojis (
+ id INTEGER PRIMARY KEY, /*though nullable, only custom emojis (with non-null ids) are stored*/
+ name TEXT NOT NULL, /*same as id*/
+ creator_id INTEGER,
+ colons BOOL,
+ managed BOOL,
+ animated BOOL,
+ available BOOL
+ )
+ )";
+
+ const char *create_members = R"(
+ CREATE TABLE IF NOT EXISTS members (
+ user_id INTEGER NOT NULL,
+ guild_id INTEGER NOT NULL,
+ nickname TEXT,
+ joined_at TEXT NOT NULL,
+ premium_since TEXT,
+ deaf BOOL NOT NULL,
+ mute BOOL NOT NULL,
+ avatar TEXT,
+ pending BOOL,
+ PRIMARY KEY(user_id, guild_id)
+ )
+ )";
+
+ const char *create_guilds = R"(
+ CREATE TABLE IF NOT EXISTS guilds (
+ id INTEGER PRIMARY KEY,
+ name TEXT NOT NULL,
+ icon TEXT NOT NULL,
+ splash TEXT,
+ owner BOOL,
+ owner_id INTEGER NOT NULL,
+ permissions INTEGER, /* new */
+ voice_region TEXT,
+ afk_id INTEGER,
+ afk_timeout INTEGER NOT NULL,
+ verification INTEGER NOT NULL,
+ notifications INTEGER NOT NULL,
+ mfa INTEGER NOT NULL,
+ application INTEGER,
+ widget BOOL,
+ widget_channel INTEGER,
+ system_flags INTEGER NOT NULL,
+ rules_channel INTEGER,
+ joined_at TEXT,
+ large BOOL,
+ unavailable BOOL,
+ member_count INTEGER,
+ max_presences INTEGER,
+ max_members INTEGER,
+ vanity TEXT,
+ description TEXT,
+ banner_hash TEXT,
+ premium_tier INTEGER NOT NULL,
+ premium_count INTEGER,
+ locale TEXT NOT NULL,
+ public_updates_id INTEGER,
+ max_video_users INTEGER,
+ approx_members INTEGER,
+ approx_presences INTEGER,
+ lazy BOOL
+ )
+ )";
+
+ const char *create_channels = R"(
+ CREATE TABLE IF NOT EXISTS channels (
+ id INTEGER PRIMARY KEY,
+ type INTEGER NOT NULL,
+ guild_id INTEGER,
+ position INTEGER,
+ name TEXT,
+ topic TEXT,
+ is_nsfw BOOL,
+ last_message_id INTEGER,
+ bitrate INTEGER,
+ user_limit INTEGER,
+ rate_limit INTEGER,
+ icon TEXT,
+ owner_id INTEGER,
+ application_id INTEGER,
+ parent_id INTEGER,
+ last_pin_timestamp TEXT,
+ archived BOOL, /* threads */
+ auto_archive INTEGER, /* threads */
+ archived_ts TEXT /* threads */
+ )
+ )";
+
+ const char *create_bans = R"(
+ CREATE TABLE IF NOT EXISTS bans (
+ guild_id INTEGER NOT NULL,
+ user_id INTEGER NOT NULL,
+ reason TEXT,
+ PRIMARY KEY(user_id, guild_id)
+ )
+ )";
+
+ const char *create_interactions = R"(
+ CREATE TABLE IF NOT EXISTS message_interactions (
+ message_id INTEGER NOT NULL,
+ interaction_id INTEGER NOT NULL,
+ type INTEGER NOT NULL,
+ name STRING NOT NULL,
+ user_id INTEGER NOT NULL,
+ PRIMARY KEY(message_id)
+ )
+ )";
+
+ const char *create_references = R"(
+ CREATE TABLE IF NOT EXISTS message_references (
+ id INTEGER NOT NULL,
+ message INTEGER,
+ channel INTEGER,
+ guild INTEGER,
+ PRIMARY KEY(id)
+ )
+ )";
+
+ const char *create_member_roles = R"(
+ CREATE TABLE IF NOT EXISTS member_roles (
+ user INTEGER NOT NULL,
+ role INTEGER NOT NULL,
+ PRIMARY KEY(user, role)
+ )
+ )";
+
+ const char *create_guild_emojis = R"(
+ CREATE TABLE IF NOT EXISTS guild_emojis (
+ guild INTEGER NOT NULL,
+ emoji INTEGER NOT NULL,
+ PRIMARY KEY(guild, emoji)
+ )
+ )";
+
+ const char *create_guild_features = R"(
+ CREATE TABLE IF NOT EXISTS guild_features (
+ guild INTEGER NOT NULL,
+ feature CHAR(63) NOT NULL,
+ PRIMARY KEY(guild, feature)
+ )
+ )";
+
+ const char *create_threads = R"(
+ CREATE TABLE IF NOT EXISTS threads (
+ guild INTEGER NOT NULL,
+ id INTEGER NOT NULL,
+ PRIMARY KEY(guild, id)
+ )
+ )";
+
+ const char *create_emoji_roles = R"(
+ CREATE TABLE IF NOT EXISTS emoji_roles (
+ emoji INTEGER NOT NULL,
+ role INTEGER NOT NULL,
+ PRIMARY KEY(emoji, role)
+ )
+ )";
+
+ const char *create_mentions = R"(
+ CREATE TABLE IF NOT EXISTS mentions (
+ message INTEGER NOT NULL,
+ user INTEGER NOT NULL,
+ PRIMARY KEY(message, user)
+ )
+ )";
+
+ const char *create_attachments = R"(
+ CREATE TABLE IF NOT EXISTS attachments (
+ message INTEGER NOT NULL,
+ id INTEGER NOT NULL,
+ filename TEXT NOT NULL,
+ size INTEGER NOT NULL,
+ url TEXT NOT NULL,
+ proxy TEXT NOT NULL,
+ height INTEGER,
+ width INTEGER,
+ PRIMARY KEY(message, id)
+ )
+ )";
+
+ const char *create_recipients = R"(
+ CREATE TABLE IF NOT EXISTS recipients (
+ channel INTEGER NOT NULL,
+ user INTEGER NOT NULL,
+ PRIMARY KEY(channel, user)
+ )
+ )";
+
+ const char *create_reactions = R"(
+ CREATE TABLE IF NOT EXISTS reactions (
+ message INTEGER NOT NULL,
+ emoji_id INTEGER,
+ name TEXT NOT NULL,
+ count INTEGER NOT NULL,
+ me BOOL NOT NULL,
+ idx INTEGER NOT NULL,
+ PRIMARY KEY(message, emoji_id, name)
+ )
+ )";
+
+ if (m_db.Execute(create_users) != SQLITE_OK) {
+ fprintf(stderr, "failed to create user table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_permissions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create permissions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_messages) != SQLITE_OK) {
+ fprintf(stderr, "failed to create messages table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_emojis) != SQLITE_OK) {
+ fprintf(stderr, "failed to create emojis table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_members) != SQLITE_OK) {
+ fprintf(stderr, "failed to create members table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guilds) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guilds table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_channels) != SQLITE_OK) {
+ fprintf(stderr, "failed to create channels table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_bans) != SQLITE_OK) {
+ fprintf(stderr, "failed to create bans table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_interactions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create interactions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_references) != SQLITE_OK) {
+ fprintf(stderr, "failed to create references table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_member_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create member roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guild_emojis) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guild emojis table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_guild_features) != SQLITE_OK) {
+ fprintf(stderr, "failed to create guild features table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_threads) != SQLITE_OK) {
+ fprintf(stderr, "failed to create threads table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_emoji_roles) != SQLITE_OK) {
+ fprintf(stderr, "failed to create emoji roles table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_mentions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create mentions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_attachments) != SQLITE_OK) {
+ fprintf(stderr, "failed to create attachments table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_recipients) != SQLITE_OK) {
+ fprintf(stderr, "failed to create recipients table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(create_reactions) != SQLITE_OK) {
+ fprintf(stderr, "failed to create reactions table: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ if (m_db.Execute(R"(
+ CREATE TRIGGER remove_zero_reactions AFTER UPDATE ON reactions WHEN new.count = 0
+ BEGIN
+ DELETE FROM reactions WHERE message = new.message AND emoji_id = new.emoji_id AND name = new.name;
+ END
+ )") != SQLITE_OK) {
+ fprintf(stderr, "failed to create reactions trigger: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ return true;
+}
+
+bool Store::CreateStatements() {
+ m_stmt_set_guild = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guilds VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild->OK()) {
+ fprintf(stderr, "failed to prepare set guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM guilds WHERE id = ?
+ )");
+ if (!m_stmt_get_guild->OK()) {
+ fprintf(stderr, "failed to prepare get guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_ids = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM guilds
+ )");
+ if (!m_stmt_get_guild_ids->OK()) {
+ fprintf(stderr, "failed to prepare get guild ids statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_guild = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM guilds WHERE id = ?
+ )");
+ if (!m_stmt_clr_guild->OK()) {
+ fprintf(stderr, "failed to prepare clear guild statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_chan = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO channels VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_chan->OK()) {
+ fprintf(stderr, "failed to prepare set channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_chan = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM channels WHERE id = ?
+ )");
+ if (!m_stmt_get_chan->OK()) {
+ fprintf(stderr, "failed to prepare get channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_chan_ids = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels
+ )");
+ if (!m_stmt_get_chan_ids->OK()) {
+ fprintf(stderr, "failed to prepare get channel ids statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_chan = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM channels WHERE id = ?
+ )");
+ if (!m_stmt_clr_chan->OK()) {
+ fprintf(stderr, "failed to prepare clear channel statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_msg = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO messages VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_msg->OK()) {
+ fprintf(stderr, "failed to prepare set message statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ // wew
+ m_stmt_get_msg = std::make_unique<Statement>(m_db, R"(
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE messages.id = ?1
+ UNION ALL
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE messages.id = (SELECT message FROM message_references WHERE id = ?1)
+ ORDER BY messages.id DESC
+ )");
+ if (!m_stmt_get_msg->OK()) {
+ fprintf(stderr, "failed to prepare get message statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_msg_ref = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO message_references VALUES (
+ ?, ?, ?, ?
+ );
+ )");
+ if (!m_stmt_set_msg_ref->OK()) {
+ fprintf(stderr, "failed to prepare set message reference statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_last_msgs = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM (
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pending = 0 ORDER BY id DESC LIMIT ?
+ ) ORDER BY id ASC
+ )");
+ if (!m_stmt_get_last_msgs->OK()) {
+ fprintf(stderr, "failed to prepare get last messages statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_user = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO users VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_user->OK()) {
+ fprintf(stderr, "failed to prepare set user statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_user = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM users WHERE id = ?
+ )");
+ if (!m_stmt_get_user->OK()) {
+ fprintf(stderr, "failed to prepare get user statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_member = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO members VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_member->OK()) {
+ fprintf(stderr, "failed to prepare set member statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_member = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM members WHERE user_id = ? AND guild_id = ?
+ )");
+ if (!m_stmt_get_member->OK()) {
+ fprintf(stderr, "failed to prepare get member statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_role = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO roles VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_role->OK()) {
+ fprintf(stderr, "failed to prepare set role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_role = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM roles WHERE id = ?
+ )");
+ if (!m_stmt_get_role->OK()) {
+ fprintf(stderr, "failed to prepare get role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM roles WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_roles->OK()) {
+ fprintf(stderr, "failed to prepare get guild roles statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_emoji = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO emojis VALUES (
+ ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_emoji->OK()) {
+ fprintf(stderr, "failed to prepare set emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_emoji = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM emojis WHERE id = ?
+ )");
+ if (!m_stmt_get_emoji->OK()) {
+ fprintf(stderr, "failed to prepare get emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_perm = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO permissions VALUES (
+ ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_perm->OK()) {
+ fprintf(stderr, "failed to prepare set permission statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_perm = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM permissions WHERE id = ? AND channel_id = ?
+ )");
+ if (!m_stmt_get_perm->OK()) {
+ fprintf(stderr, "failed to prepare get permission statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_ban = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO bans VALUES (
+ ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_ban->OK()) {
+ fprintf(stderr, "failed to prepare set ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_ban = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM bans WHERE guild_id = ? AND user_id = ?
+ )");
+ if (!m_stmt_get_ban->OK()) {
+ fprintf(stderr, "failed to prepare get ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_bans = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM bans WHERE guild_id = ?
+ )");
+ if (!m_stmt_get_bans->OK()) {
+ fprintf(stderr, "failed to prepare get bans statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_ban = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM bans WHERE guild_id = ? AND user_id = ?
+ )");
+ if (!m_stmt_clr_ban->OK()) {
+ fprintf(stderr, "failed to prepare clear ban statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_interaction = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO message_interactions VALUES (
+ ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_interaction->OK()) {
+ fprintf(stderr, "failed to prepare set interaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_member_roles = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO member_roles VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_member_roles->OK()) {
+ fprintf(stderr, "faile to prepare set member roles statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_member_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM roles, member_roles
+ WHERE roles.id = member_roles.role
+ AND member_roles.user = ?
+ AND roles.guild = ?
+ )");
+ if (!m_stmt_get_member_roles->OK()) {
+ fprintf(stderr, "failed to prepare get member role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_guild_emoji = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guild_emojis VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild_emoji->OK()) {
+ fprintf(stderr, "failed to prepare set guild emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_emojis = std::make_unique<Statement>(m_db, R"(
+ SELECT emoji FROM guild_emojis WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_emojis->OK()) {
+ fprintf(stderr, "failed to prepare get guild emojis statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_guild_emoji = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM guild_emojis WHERE guild = ? AND emoji = ?
+ )");
+ if (!m_stmt_clr_guild_emoji->OK()) {
+ fprintf(stderr, "failed to prepare clear guild emoji statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_guild_feature = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO guild_features VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_guild_feature->OK()) {
+ fprintf(stderr, "failed to prepare set guild feature statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_features = std::make_unique<Statement>(m_db, R"(
+ SELECT feature FROM guild_features WHERE guild = ?
+ )");
+ if (!m_stmt_get_guild_features->OK()) {
+ fprintf(stderr, "failed to prepare get guild features statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_guild_chans = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels WHERE guild_id = ?
+ )");
+ if (!m_stmt_get_guild_chans->OK()) {
+ fprintf(stderr, "failed to prepare get guild channels statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_thread = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO threads VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_thread->OK()) {
+ fprintf(stderr, "failed to prepare set thread statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_threads = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM threads WHERE guild = ?
+ )");
+ if (!m_stmt_get_threads->OK()) {
+ fprintf(stderr, "failed to prepare get threads statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_active_threads = std::make_unique<Statement>(m_db, R"(
+ SELECT id FROM channels WHERE parent_id = ? AND (type = 10 OR type = 11 OR type = 12) AND archived = FALSE
+ )");
+ if (!m_stmt_get_active_threads->OK()) {
+ fprintf(stderr, "faile to prepare get active threads statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_messages_before = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM (
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pending = 0 AND messages.id < ? ORDER BY id DESC LIMIT ?
+ ) ORDER BY id ASC
+ )");
+ if (!m_stmt_get_messages_before->OK()) {
+ fprintf(stderr, "failed to prepare get messages before statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_pins = std::make_unique<Statement>(m_db, R"(
+ SELECT messages.*,
+ message_interactions.interaction_id,
+ message_interactions.name,
+ message_interactions.type,
+ message_interactions.user_id,
+ attachments.id,
+ attachments.filename,
+ attachments.size,
+ attachments.url,
+ attachments.proxy,
+ attachments.height,
+ attachments.width,
+ message_references.message,
+ message_references.channel,
+ message_references.guild
+ FROM messages
+ LEFT OUTER JOIN
+ message_interactions
+ ON messages.id = message_interactions.message_id
+ LEFT OUTER JOIN
+ attachments
+ ON messages.id = attachments.message
+ LEFT OUTER JOIN
+ message_references
+ ON messages.id = message_references.id
+ WHERE channel_id = ? AND pinned = 1 ORDER BY id ASC
+ )");
+ if (!m_stmt_get_pins->OK()) {
+ fprintf(stderr, "failed to prepare get pins statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_emoji_role = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO emoji_roles VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_emoji_role->OK()) {
+ fprintf(stderr, "failed to prepare set emoji role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_emoji_roles = std::make_unique<Statement>(m_db, R"(
+ SELECT role FROM emoji_roles WHERE emoji = ?
+ )");
+ if (!m_stmt_get_emoji_roles->OK()) {
+ fprintf(stderr, "failed to prepare get emoji role statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_mention = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO mentions VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_mention->OK()) {
+ fprintf(stderr, "failed to prepare set mention statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_mentions = std::make_unique<Statement>(m_db, R"(
+ SELECT user FROM mentions WHERE message = ?
+ )");
+ if (!m_stmt_get_mentions->OK()) {
+ fprintf(stderr, "failed to prepare get mentions statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_attachment = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO attachments VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ )");
+ if (!m_stmt_set_attachment->OK()) {
+ fprintf(stderr, "failed to prepare set attachment statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_attachments = std::make_unique<Statement>(m_db, R"(
+ SELECT * FROM attachments WHERE message = ?
+ )");
+ if (!m_stmt_get_attachments->OK()) {
+ fprintf(stderr, "failed to prepare get attachments statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_set_recipient = std::make_unique<Statement>(m_db, R"(
+ REPLACE INTO recipients VALUES (
+ ?, ?
+ )
+ )");
+ if (!m_stmt_set_recipient->OK()) {
+ fprintf(stderr, "failed to prepare set recipient statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_recipients = std::make_unique<Statement>(m_db, R"(
+ SELECT user FROM recipients WHERE channel = ?
+ )");
+ if (!m_stmt_get_recipients->OK()) {
+ fprintf(stderr, "failed to prepare get recipients statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_clr_recipient = std::make_unique<Statement>(m_db, R"(
+ DELETE FROM recipients WHERE channel = ? AND user = ?
+ )");
+ if (!m_stmt_clr_recipient->OK()) {
+ fprintf(stderr, "failed to prepare clear recipient statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ // probably not the best way to do this lol but i just want one statement i guess
+ m_stmt_add_reaction = std::make_unique<Statement>(m_db, R"(
+ INSERT OR REPLACE INTO reactions VALUES (
+ ?1, ?2, ?3,
+ COALESCE(
+ (SELECT count FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ 0
+ ) + ?4,
+ COALESCE(
+ ?5,
+ (SELECT me FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ false
+ ),
+ COALESCE(
+ ?6,
+ (SELECT idx FROM reactions WHERE message = ?1 AND emoji_id = ?2 AND name = ?3),
+ (SELECT MAX(idx) + 1 FROM reactions WHERE message = ?1),
+ 0
+ )
+ )
+ )");
+ if (!m_stmt_add_reaction->OK()) {
+ fprintf(stderr, "failed to prepare add reaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_sub_reaction = std::make_unique<Statement>(m_db, R"(
+ UPDATE reactions
+ SET count = count - 1,
+ me = COALESCE(?4, me)
+ WHERE message = ?1 AND emoji_id = ?2 AND name = ?3
+ )");
+ if (!m_stmt_sub_reaction->OK()) {
+ fprintf(stderr, "failed to prepare sub reaction statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ m_stmt_get_reactions = std::make_unique<Statement>(m_db, R"(
+ SELECT emoji_id, name, count, me, idx FROM reactions WHERE message = ?
+ )");
+ if (!m_stmt_get_reactions->OK()) {
+ fprintf(stderr, "failed to prepare get reactions statement: %s\n", m_db.ErrStr());
+ return false;
+ }
+
+ return true;
+}
+
+Store::Database::Database(const char *path) {
+ m_err = sqlite3_open(path, &m_db);
+}
+
+Store::Database::~Database() {
+ Close();
+}
+
+int Store::Database::Close() {
+ if (m_db == nullptr) return m_err;
+ m_signal_close.emit();
+ m_err = sqlite3_close(m_db);
+ m_db = nullptr;
+ return m_err;
+}
+
+int Store::Database::StartTransaction() {
+ return m_err = Execute("BEGIN TRANSACTION");
+}
+
+int Store::Database::EndTransaction() {
+ return m_err = Execute("COMMIT");
+}
+
+int Store::Database::Execute(const char *command) {
+ return m_err = sqlite3_exec(m_db, command, nullptr, nullptr, nullptr);
+}
+
+int Store::Database::Error() const {
+ return m_err;
+}
+
+bool Store::Database::OK() const {
+ return Error() == SQLITE_OK;
+}
+
+const char *Store::Database::ErrStr() const {
+ const char *errstr = sqlite3_errstr(m_err);
+ const char *errmsg = sqlite3_errmsg(m_db);
+ std::string tmp = errstr + std::string("\n\t") + errmsg;
+ tmp.copy(m_err_scratch, sizeof(m_err_scratch) - 1);
+ m_err_scratch[std::min(tmp.size(), sizeof(m_err_scratch) - 1)] = '\0';
+ return m_err_scratch;
+}
+
+int Store::Database::SetError(int err) {
+ return m_err = err;
+}
+
+sqlite3 *Store::Database::obj() {
+ return m_db;
+}
+
+Store::Database::type_signal_close Store::Database::signal_close() {
+ return m_signal_close;
+}
+
+Store::Statement::Statement(Database &db, const char *command)
+ : m_db(&db) {
+ if (m_db->SetError(sqlite3_prepare_v2(m_db->obj(), command, -1, &m_stmt, nullptr)) != SQLITE_OK) return;
+ m_db->signal_close().connect([this] {
+ sqlite3_finalize(m_stmt);
+ m_stmt = nullptr;
+ });
+}
+
+Store::Statement::~Statement() {
+ sqlite3_finalize(m_stmt);
+}
+
+bool Store::Statement::OK() const {
+ return m_stmt != nullptr;
+}
+
+int Store::Statement::Bind(int index, Snowflake id) {
+ return Bind(index, static_cast<uint64_t>(id));
+}
+
+int Store::Statement::Bind(int index, const char *str, size_t len) {
+ if (len == -1) len = strlen(str);
+ return m_db->SetError(sqlite3_bind_blob(m_stmt, index, str, len, SQLITE_TRANSIENT));
+}
+
+int Store::Statement::Bind(int index, const std::string &str) {
+ return m_db->SetError(sqlite3_bind_blob(m_stmt, index, str.c_str(), str.size(), SQLITE_TRANSIENT));
+}
+
+int Store::Statement::Bind(int index) {
+ return m_db->SetError(sqlite3_bind_null(m_stmt, index));
+}
+
+void Store::Statement::Get(int index, Snowflake &out) const {
+ out = static_cast<uint64_t>(sqlite3_column_int64(m_stmt, index));
+}
+
+void Store::Statement::Get(int index, std::string &out) const {
+ const unsigned char *ptr = sqlite3_column_text(m_stmt, index);
+ if (ptr == nullptr)
+ out = "";
+ else
+ out = reinterpret_cast<const char *>(ptr);
+}
+
+bool Store::Statement::IsNull(int index) const {
+ return sqlite3_column_type(m_stmt, index) == SQLITE_NULL;
+}
+
+int Store::Statement::Step() {
+ return m_db->SetError(sqlite3_step(m_stmt));
+}
+
+bool Store::Statement::Insert() {
+ return m_db->SetError(sqlite3_step(m_stmt)) == SQLITE_DONE;
+}
+
+bool Store::Statement::FetchOne() {
+ return m_db->SetError(sqlite3_step(m_stmt)) == SQLITE_ROW;
+}
+
+int Store::Statement::Reset() {
+ if (m_db->SetError(sqlite3_reset(m_stmt)) != SQLITE_OK)
+ return m_db->Error();
+ if (m_db->SetError(sqlite3_clear_bindings(m_stmt)) != SQLITE_OK)
+ return m_db->Error();
+ return m_db->Error();
+}
+
+sqlite3_stmt *Store::Statement::obj() {
+ return m_stmt;
+}
diff --git a/src/discord/store.hpp b/src/discord/store.hpp
new file mode 100644
index 0000000..715f280
--- /dev/null
+++ b/src/discord/store.hpp
@@ -0,0 +1,304 @@
+#pragma once
+#include "util.hpp"
+#include "objects.hpp"
+#include <unordered_map>
+#include <unordered_set>
+#include <mutex>
+#include <filesystem>
+#include <sqlite3.h>
+
+#ifdef GetMessage // fuck you windows.h
+ #undef GetMessage
+#endif
+
+class Store {
+public:
+ Store(bool mem_store = false);
+ ~Store();
+
+ bool IsValid() const;
+
+ void SetUser(Snowflake id, const UserData &user);
+ void SetChannel(Snowflake id, const ChannelData &chan);
+ void SetGuild(Snowflake id, const GuildData &guild);
+ void SetRole(Snowflake guild_id, const RoleData &role);
+ void SetMessage(Snowflake id, const Message &message);
+ void SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMember &data);
+ void SetPermissionOverwrite(Snowflake channel_id, Snowflake id, const PermissionOverwrite &perm);
+ void SetEmoji(Snowflake id, const EmojiData &emoji);
+ void SetBan(Snowflake guild_id, Snowflake user_id, const BanData &ban);
+
+ std::optional<ChannelData> GetChannel(Snowflake id) const;
+ std::optional<EmojiData> GetEmoji(Snowflake id) const;
+ std::optional<GuildData> GetGuild(Snowflake id) const;
+ std::optional<GuildMember> GetGuildMember(Snowflake guild_id, Snowflake user_id) const;
+ std::optional<Message> GetMessage(Snowflake id) const;
+ std::optional<PermissionOverwrite> GetPermissionOverwrite(Snowflake channel_id, Snowflake id) const;
+ std::optional<RoleData> GetRole(Snowflake id) const;
+ std::optional<UserData> GetUser(Snowflake id) const;
+ std::optional<BanData> GetBan(Snowflake guild_id, Snowflake user_id) const;
+ std::vector<BanData> GetBans(Snowflake guild_id) const;
+
+ std::vector<Message> GetLastMessages(Snowflake id, size_t num) const;
+ std::vector<Message> GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const;
+ std::vector<Message> GetPinnedMessages(Snowflake channel_id) const;
+ std::vector<ChannelData> GetActiveThreads(Snowflake channel_id) const; // public
+
+ void AddReaction(const MessageReactionAddObject &data, bool byself);
+ void RemoveReaction(const MessageReactionRemoveObject &data, bool byself);
+
+ void ClearGuild(Snowflake id);
+ void ClearChannel(Snowflake id);
+ void ClearBan(Snowflake guild_id, Snowflake user_id);
+ void ClearRecipient(Snowflake channel_id, Snowflake user_id);
+
+ std::unordered_set<Snowflake> GetChannels() const;
+ std::unordered_set<Snowflake> GetGuilds() const;
+
+ void ClearAll();
+
+ void BeginTransaction();
+ void EndTransaction();
+
+private:
+ class Statement;
+ class Database {
+ public:
+ Database(const char *path);
+ ~Database();
+
+ int Close();
+ int StartTransaction();
+ int EndTransaction();
+ int Execute(const char *command);
+ int Error() const;
+ bool OK() const;
+ const char *ErrStr() const;
+ int SetError(int err);
+ sqlite3 *obj();
+
+ private:
+ sqlite3 *m_db;
+ int m_err = SQLITE_OK;
+ mutable char m_err_scratch[256] { 0 };
+
+ // stupid shit i dont like to allow closing properly
+ using type_signal_close = sigc::signal<void>;
+ type_signal_close m_signal_close;
+
+ public:
+ type_signal_close signal_close();
+ };
+
+ class Statement {
+ public:
+ Statement() = delete;
+ Statement(const Statement &other) = delete;
+ Statement(Database &db, const char *command);
+ ~Statement();
+ Statement &operator=(Statement &other) = delete;
+
+ bool OK() const;
+
+ int Bind(int index, Snowflake id);
+ int Bind(int index, const char *str, size_t len = -1);
+ int Bind(int index, const std::string &str);
+ int Bind(int index);
+
+ template<typename T>
+ int Bind(int index, std::optional<T> opt) {
+ if (opt.has_value())
+ return Bind(index, opt.value());
+ else
+ return Bind(index);
+ }
+
+ template<typename Iter>
+ int BindIDsAsJSON(int index, Iter start, Iter end) {
+ std::vector<Snowflake> x;
+ for (Iter it = start; it != end; it++) {
+ x.push_back((*it).ID);
+ }
+ return Bind(index, nlohmann::json(x).dump());
+ }
+
+ template<typename T>
+ int BindAsJSONArray(int index, const std::optional<T> &obj) {
+ if (obj.has_value())
+ return Bind(index, nlohmann::json(obj.value()).dump());
+ else
+ return Bind(index, std::string("[]"));
+ }
+
+ template<typename T>
+ int BindAsJSON(int index, const T &obj) {
+ return Bind(index, nlohmann::json(obj).dump());
+ }
+
+ template<typename T>
+ inline typename std::enable_if<std::is_enum<T>::value, int>::type
+ Bind(int index, T val) {
+ return Bind(index, static_cast<typename std::underlying_type<T>::type>(val));
+ }
+
+ template<typename T>
+ typename std::enable_if<std::is_integral<T>::value, int>::type
+ Bind(int index, T val) {
+ return m_db->SetError(sqlite3_bind_int64(m_stmt, index, val));
+ }
+
+ template<typename T>
+ int BindAsJSON(int index, const std::optional<T> &obj) {
+ if (obj.has_value())
+ return Bind(index, nlohmann::json(obj.value()).dump());
+ else
+ return Bind(index);
+ }
+
+ template<typename T>
+ typename std::enable_if<std::is_integral<T>::value>::type
+ Get(int index, T &out) const {
+ out = static_cast<T>(sqlite3_column_int64(m_stmt, index));
+ }
+
+ void Get(int index, Snowflake &out) const;
+ void Get(int index, std::string &out) const;
+
+ template<typename T>
+ void GetJSON(int index, std::optional<T> &out) const {
+ if (IsNull(index))
+ out = std::nullopt;
+ else {
+ std::string stuff;
+ Get(index, stuff);
+ if (stuff == "")
+ out = std::nullopt;
+ else
+ out = nlohmann::json::parse(stuff).get<T>();
+ }
+ }
+
+ template<typename T>
+ void GetJSON(int index, T &out) const {
+ std::string stuff;
+ Get(index, stuff);
+ nlohmann::json::parse(stuff).get_to(out);
+ }
+
+ template<typename T>
+ void Get(int index, std::optional<T> &out) const {
+ if (IsNull(index))
+ out = std::nullopt;
+ else {
+ T tmp;
+ Get(index, tmp);
+ out = std::optional<T>(std::move(tmp));
+ }
+ }
+
+ template<typename T>
+ inline typename std::enable_if<std::is_enum<T>::value, void>::type
+ Get(int index, T &val) const {
+ typename std::underlying_type<T>::type tmp;
+ Get(index, tmp);
+ val = static_cast<T>(tmp);
+ }
+
+ template<typename T>
+ void GetIDOnlyStructs(int index, std::optional<std::vector<T>> &out) const {
+ out.emplace();
+ std::string str;
+ Get(index, str);
+ for (const auto &id : nlohmann::json::parse(str))
+ out->emplace_back().ID = id.get<Snowflake>();
+ }
+
+ template<typename T, typename OutputIt>
+ void GetArray(int index, OutputIt first) const {
+ std::string str;
+ Get(index, str);
+ for (const auto &id : nlohmann::json::parse(str))
+ *first++ = id.get<T>();
+ }
+
+ bool IsNull(int index) const;
+ int Step();
+ bool Insert();
+ bool FetchOne();
+ int Reset();
+
+ sqlite3_stmt *obj();
+
+ private:
+ Database *m_db;
+ sqlite3_stmt *m_stmt;
+ };
+
+ Message GetMessageBound(std::unique_ptr<Statement> &stmt) const;
+ RoleData GetRoleBound(std::unique_ptr<Statement> &stmt) const;
+
+ void SetMessageInteractionPair(Snowflake message_id, const MessageInteractionData &interaction);
+
+ bool CreateTables();
+ bool CreateStatements();
+
+ bool m_ok = true;
+
+ std::filesystem::path m_db_path;
+ Database m_db;
+#define STMT(x) mutable std::unique_ptr<Statement> m_stmt_##x
+ STMT(set_guild);
+ STMT(get_guild);
+ STMT(get_guild_ids);
+ STMT(clr_guild);
+ STMT(set_chan);
+ STMT(get_chan);
+ STMT(get_chan_ids);
+ STMT(clr_chan);
+ STMT(set_msg);
+ STMT(get_msg);
+ STMT(set_msg_ref);
+ STMT(get_last_msgs);
+ STMT(set_user);
+ STMT(get_user);
+ STMT(set_member);
+ STMT(get_member);
+ STMT(set_role);
+ STMT(get_role);
+ STMT(get_guild_roles);
+ STMT(set_emoji);
+ STMT(get_emoji);
+ STMT(set_perm);
+ STMT(get_perm);
+ STMT(set_ban);
+ STMT(get_ban);
+ STMT(get_bans);
+ STMT(clr_ban);
+ STMT(set_interaction);
+ STMT(set_member_roles);
+ STMT(get_member_roles);
+ STMT(set_guild_emoji);
+ STMT(get_guild_emojis);
+ STMT(clr_guild_emoji);
+ STMT(set_guild_feature);
+ STMT(get_guild_features);
+ STMT(get_guild_chans);
+ STMT(set_thread);
+ STMT(get_threads);
+ STMT(get_active_threads);
+ STMT(get_messages_before);
+ STMT(get_pins);
+ STMT(set_emoji_role);
+ STMT(get_emoji_roles);
+ STMT(set_mention);
+ STMT(get_mentions);
+ STMT(set_attachment);
+ STMT(get_attachments);
+ STMT(set_recipient);
+ STMT(get_recipients);
+ STMT(clr_recipient);
+ STMT(add_reaction);
+ STMT(sub_reaction);
+ STMT(get_reactions);
+#undef STMT
+};
diff --git a/discord/user.cpp b/src/discord/user.cpp
index a68346e..fae212d 100644
--- a/discord/user.cpp
+++ b/src/discord/user.cpp
@@ -1,5 +1,5 @@
#include "user.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
bool UserData::IsDeleted() const {
return Discriminator == "0000";
diff --git a/discord/user.hpp b/src/discord/user.hpp
index d4711fa..d4711fa 100644
--- a/discord/user.hpp
+++ b/src/discord/user.hpp
diff --git a/discord/usersettings.cpp b/src/discord/usersettings.cpp
index e4ab41a..e4ab41a 100644
--- a/discord/usersettings.cpp
+++ b/src/discord/usersettings.cpp
diff --git a/discord/usersettings.hpp b/src/discord/usersettings.hpp
index 6d37b3c..6d37b3c 100644
--- a/discord/usersettings.hpp
+++ b/src/discord/usersettings.hpp
diff --git a/discord/webhook.cpp b/src/discord/webhook.cpp
index 4e8b422..4e8b422 100644
--- a/discord/webhook.cpp
+++ b/src/discord/webhook.cpp
diff --git a/discord/webhook.hpp b/src/discord/webhook.hpp
index f0214df..f0214df 100644
--- a/discord/webhook.hpp
+++ b/src/discord/webhook.hpp
diff --git a/discord/websocket.cpp b/src/discord/websocket.cpp
index ff50cd3..c7e43e9 100644
--- a/discord/websocket.cpp
+++ b/src/discord/websocket.cpp
@@ -15,6 +15,14 @@ void Websocket::SetUserAgent(std::string agent) {
m_agent = agent;
}
+bool Websocket::GetPrintMessages() const noexcept {
+ return m_print_messages;
+}
+
+void Websocket::SetPrintMessages(bool show) noexcept {
+ m_print_messages = show;
+}
+
void Websocket::Stop() {
Stop(ix::WebSocketCloseConstants::kNormalClosureCode);
}
@@ -29,7 +37,8 @@ bool Websocket::IsOpen() const {
}
void Websocket::Send(const std::string &str) {
- printf("sending %s\n", str.c_str());
+ if (m_print_messages)
+ printf("sending %s\n", str.c_str());
m_websocket.sendText(str);
}
diff --git a/discord/websocket.hpp b/src/discord/websocket.hpp
index e6a6489..26cd5d4 100644
--- a/discord/websocket.hpp
+++ b/src/discord/websocket.hpp
@@ -13,6 +13,9 @@ public:
void SetUserAgent(std::string agent);
+ bool GetPrintMessages() const noexcept;
+ void SetPrintMessages(bool show) noexcept;
+
void Send(const std::string &str);
void Send(const nlohmann::json &j);
void Stop();
@@ -26,9 +29,9 @@ private:
std::string m_agent;
public:
- typedef sigc::signal<void> type_signal_open;
- typedef sigc::signal<void, uint16_t> type_signal_close;
- typedef sigc::signal<void, std::string> type_signal_message;
+ using type_signal_open = sigc::signal<void>;
+ using type_signal_close = sigc::signal<void, uint16_t>;
+ using type_signal_message = sigc::signal<void, std::string>;
type_signal_open signal_open();
type_signal_close signal_close();
@@ -38,4 +41,6 @@ private:
type_signal_open m_signal_open;
type_signal_close m_signal_close;
type_signal_message m_signal_message;
+
+ bool m_print_messages = true;
};
diff --git a/emojis.cpp b/src/emojis.cpp
index dd7193a..dd7193a 100644
--- a/emojis.cpp
+++ b/src/emojis.cpp
diff --git a/emojis.hpp b/src/emojis.hpp
index 61b8a71..61b8a71 100644
--- a/emojis.hpp
+++ b/src/emojis.hpp
diff --git a/filecache.cpp b/src/filecache.cpp
index a731750..e04fbcb 100644
--- a/filecache.cpp
+++ b/src/filecache.cpp
@@ -142,7 +142,7 @@ void FileCacheWorkerThread::loop() {
m_cv.wait(lock);
}
- static const auto concurrency = static_cast<size_t>(Abaddon::Get().GetSettings().GetCacheHTTPConcurrency());
+ static const auto concurrency = static_cast<size_t>(Abaddon::Get().GetSettings().CacheHTTPConcurrency);
if (m_handles.size() < concurrency) {
std::optional<QueueEntry> entry;
m_queue_mutex.lock();
diff --git a/filecache.hpp b/src/filecache.hpp
index d25fdb3..d25fdb3 100644
--- a/filecache.hpp
+++ b/src/filecache.hpp
diff --git a/http.cpp b/src/http.cpp
index 790add7..790add7 100644
--- a/http.cpp
+++ b/src/http.cpp
diff --git a/http.hpp b/src/http.hpp
index c2e2765..c2e2765 100644
--- a/http.hpp
+++ b/src/http.hpp
diff --git a/imgmanager.cpp b/src/imgmanager.cpp
index b97083c..b97083c 100644
--- a/imgmanager.cpp
+++ b/src/imgmanager.cpp
diff --git a/imgmanager.hpp b/src/imgmanager.hpp
index eb8a590..eb8a590 100644
--- a/imgmanager.hpp
+++ b/src/imgmanager.hpp
diff --git a/platform.cpp b/src/platform.cpp
index dfbea3b..dfbea3b 100644
--- a/platform.cpp
+++ b/src/platform.cpp
diff --git a/platform.hpp b/src/platform.hpp
index e321d4e..e321d4e 100644
--- a/platform.hpp
+++ b/src/platform.hpp
diff --git a/src/settings.cpp b/src/settings.cpp
new file mode 100644
index 0000000..6820ed0
--- /dev/null
+++ b/src/settings.cpp
@@ -0,0 +1,115 @@
+#include "settings.hpp"
+#include <filesystem>
+#include <fstream>
+
+SettingsManager::SettingsManager(const std::string &filename)
+ : m_filename(filename) {
+ if (!std::filesystem::exists(filename)) {
+ std::fstream fs;
+ fs.open(filename, std::ios::out);
+ fs.close();
+ }
+
+ try {
+ m_ok = m_file.load_from_file(m_filename, Glib::KEY_FILE_KEEP_COMMENTS);
+ } catch (const Glib::Error &e) {
+ fprintf(stderr, "error opening settings KeyFile: %s\n", e.what().c_str());
+ m_ok = false;
+ }
+
+ if (m_ok) ReadSettings();
+}
+
+void SettingsManager::ReadSettings() {
+#define SMBOOL(section, key, var) \
+ try { \
+ m_settings.var = m_file.get_boolean(section, key); \
+ } catch (...) {}
+#define SMSTR(section, key, var) \
+ try { \
+ m_settings.var = m_file.get_string(section, key); \
+ } catch (...) {}
+#define SMINT(section, key, var) \
+ try { \
+ m_settings.var = m_file.get_integer(section, key); \
+ } catch (...) {}
+
+ SMSTR("discord", "api_base", APIBaseURL);
+ SMSTR("discord", "gateway", GatewayURL);
+ SMSTR("discord", "token", DiscordToken);
+ SMBOOL("discord", "memory_db", UseMemoryDB);
+ SMBOOL("discord", "prefetch", Prefetch);
+ SMSTR("gui", "css", MainCSS);
+ SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly);
+ SMBOOL("gui", "animations", ShowAnimations);
+ SMBOOL("gui", "custom_emojis", ShowCustomEmojis);
+ SMBOOL("gui", "member_list_discriminator", ShowMemberListDiscriminators);
+ SMBOOL("gui", "owner_crown", ShowOwnerCrown);
+ SMBOOL("gui", "save_state", SaveState);
+ SMBOOL("gui", "stock_emojis", ShowStockEmojis);
+ SMINT("http", "concurrent", CacheHTTPConcurrency);
+ SMSTR("http", "user_agent", UserAgent);
+ SMSTR("style", "expandercolor", ChannelsExpanderColor);
+ SMSTR("style", "linkcolor", LinkColor);
+ SMSTR("style", "nsfwchannelcolor", NSFWChannelColor);
+
+#undef SMBOOL
+#undef SMSTR
+#undef SMINT
+
+ m_read_settings = m_settings;
+}
+
+bool SettingsManager::IsValid() const {
+ return m_ok;
+}
+
+SettingsManager::Settings &SettingsManager::GetSettings() {
+ return m_settings;
+}
+
+void SettingsManager::Close() {
+ if (m_ok) {
+ // save anything that changed
+ // (futureproofing since only DiscordToken can actually change)
+#define SMSTR(section, key, var) \
+ if (m_settings.var != m_read_settings.var) \
+ m_file.set_string(section, key, m_settings.var);
+#define SMBOOL(section, key, var) \
+ if (m_settings.var != m_read_settings.var) \
+ m_file.set_boolean(section, key, m_settings.var);
+#define SMINT(section, key, var) \
+ if (m_settings.var != m_read_settings.var) \
+ m_file.set_integer(section, key, m_settings.var);
+
+ SMSTR("discord", "api_base", APIBaseURL);
+ SMSTR("discord", "gateway", GatewayURL);
+ SMSTR("discord", "token", DiscordToken);
+ SMBOOL("discord", "memory_db", UseMemoryDB);
+ SMBOOL("discord", "prefetch", Prefetch);
+ SMSTR("gui", "css", MainCSS);
+ SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly);
+ SMBOOL("gui", "animations", ShowAnimations);
+ SMBOOL("gui", "custom_emojis", ShowCustomEmojis);
+ SMBOOL("gui", "member_list_discriminator", ShowMemberListDiscriminators);
+ SMBOOL("gui", "owner_crown", ShowOwnerCrown);
+ SMBOOL("gui", "save_state", SaveState);
+ SMBOOL("gui", "stock_emojis", ShowStockEmojis);
+ SMINT("http", "concurrent", CacheHTTPConcurrency);
+ SMSTR("http", "user_agent", UserAgent);
+ SMSTR("style", "expandercolor", ChannelsExpanderColor);
+ SMSTR("style", "linkcolor", LinkColor);
+ SMSTR("style", "nsfwchannelcolor", NSFWChannelColor);
+
+#undef SMSTR
+#undef SMBOOL
+#undef SMINT
+
+ try {
+ if (!m_file.save_to_file(m_filename))
+ fputs("failed to save settings KeyFile", stderr);
+ } catch (const Glib::Error &e) {
+ fprintf(stderr, "failed to save settings KeyFile: %s\n", e.what().c_str());
+ }
+ }
+}
diff --git a/src/settings.hpp b/src/settings.hpp
new file mode 100644
index 0000000..ca2303f
--- /dev/null
+++ b/src/settings.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include <string>
+#include <type_traits>
+#include <glibmm/keyfile.h>
+
+class SettingsManager {
+public:
+ struct Settings {
+ // [discord]
+ std::string APIBaseURL { "https://discord.com/api/v9" };
+ std::string GatewayURL { "wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream" };
+ std::string DiscordToken;
+ bool UseMemoryDB { false };
+ bool Prefetch { false };
+
+ // [gui]
+ std::string MainCSS { "main.css" };
+ bool AnimatedGuildHoverOnly { true };
+ bool ShowAnimations { true };
+ bool ShowCustomEmojis { true };
+ bool ShowMemberListDiscriminators { true };
+ bool ShowOwnerCrown { true };
+ bool SaveState { true };
+#ifdef _WIN32
+ bool ShowStockEmojis { false };
+#else
+ bool ShowStockEmojis { true };
+#endif
+
+ // [http]
+ int CacheHTTPConcurrency { 20 };
+ std::string UserAgent { "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" };
+
+ // [style]
+ // TODO: convert to StyleProperty
+ std::string LinkColor { "rgba(40, 200, 180, 255)" };
+ std::string ChannelsExpanderColor { "rgba(255, 83, 112, 255)" };
+ std::string NSFWChannelColor { "#ed6666" };
+ };
+
+ SettingsManager(const std::string &filename);
+
+ void Close();
+ bool IsValid() const;
+ Settings &GetSettings();
+
+private:
+ void ReadSettings();
+
+ bool m_ok;
+ std::string m_filename;
+ Glib::KeyFile m_file;
+ Settings m_settings;
+ Settings m_read_settings;
+};
diff --git a/state.cpp b/src/state.cpp
index 043d181..043d181 100644
--- a/state.cpp
+++ b/src/state.cpp
diff --git a/state.hpp b/src/state.hpp
index 230808f..230808f 100644
--- a/state.hpp
+++ b/src/state.hpp
diff --git a/util.cpp b/src/util.cpp
index 1a7182d..1a7182d 100644
--- a/util.cpp
+++ b/src/util.cpp
diff --git a/util.hpp b/src/util.hpp
index feaf08d..feaf08d 100644
--- a/util.hpp
+++ b/src/util.hpp
diff --git a/windows/guildsettings/auditlogpane.cpp b/src/windows/guildsettings/auditlogpane.cpp
index 5a6cb82..08f99da 100644
--- a/windows/guildsettings/auditlogpane.cpp
+++ b/src/windows/guildsettings/auditlogpane.cpp
@@ -1,5 +1,5 @@
#include "auditlogpane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
using namespace std::string_literals;
diff --git a/windows/guildsettings/auditlogpane.hpp b/src/windows/guildsettings/auditlogpane.hpp
index 89749aa..ac12321 100644
--- a/windows/guildsettings/auditlogpane.hpp
+++ b/src/windows/guildsettings/auditlogpane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/objects.hpp"
+#include "discord/objects.hpp"
class GuildSettingsAuditLogPane : public Gtk::ScrolledWindow {
public:
diff --git a/windows/guildsettings/banspane.cpp b/src/windows/guildsettings/banspane.cpp
index 0fede20..97a70c4 100644
--- a/windows/guildsettings/banspane.cpp
+++ b/src/windows/guildsettings/banspane.cpp
@@ -1,5 +1,5 @@
#include "banspane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
// gtk_list_store_set_value: assertion 'column >= 0 && column < priv->n_columns' failed
// dont care to figure out why this happens cuz it doesnt seem to break anything
diff --git a/windows/guildsettings/banspane.hpp b/src/windows/guildsettings/banspane.hpp
index ead118e..b2420a9 100644
--- a/windows/guildsettings/banspane.hpp
+++ b/src/windows/guildsettings/banspane.hpp
@@ -1,7 +1,7 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/snowflake.hpp"
-#include "../../discord/ban.hpp"
+#include "discord/snowflake.hpp"
+#include "discord/ban.hpp"
class GuildSettingsBansPane : public Gtk::Box {
public:
diff --git a/windows/guildsettings/emojispane.cpp b/src/windows/guildsettings/emojispane.cpp
index e64f60d..57b697c 100644
--- a/windows/guildsettings/emojispane.cpp
+++ b/src/windows/guildsettings/emojispane.cpp
@@ -1,6 +1,6 @@
#include "emojispane.hpp"
-#include "../../abaddon.hpp"
-#include "../../components/cellrendererpixbufanimation.hpp"
+#include "abaddon.hpp"
+#include "components/cellrendererpixbufanimation.hpp"
GuildSettingsEmojisPane::GuildSettingsEmojisPane(Snowflake guild_id)
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
@@ -130,8 +130,7 @@ void GuildSettingsEmojisPane::AddEmojiRow(const EmojiData &emoji) {
else
row[m_columns.m_col_available] = "Yes";
- static bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
- if (show_animations && emoji.IsAnimated.has_value() && *emoji.IsAnimated) {
+ if (Abaddon::Get().GetSettings().ShowAnimations && emoji.IsAnimated.has_value() && *emoji.IsAnimated) {
const auto cb = [this, id = emoji.ID](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
for (auto &row : m_model->children()) {
if (static_cast<Snowflake>(row[m_columns.m_col_id]) == id) {
diff --git a/windows/guildsettings/emojispane.hpp b/src/windows/guildsettings/emojispane.hpp
index 19203c7..1c0edd1 100644
--- a/windows/guildsettings/emojispane.hpp
+++ b/src/windows/guildsettings/emojispane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/emoji.hpp"
+#include "discord/emoji.hpp"
class GuildSettingsEmojisPane : public Gtk::Box {
public:
diff --git a/windows/guildsettings/infopane.cpp b/src/windows/guildsettings/infopane.cpp
index d75def1..9ef116f 100644
--- a/windows/guildsettings/infopane.cpp
+++ b/src/windows/guildsettings/infopane.cpp
@@ -1,5 +1,5 @@
#include "infopane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
#include <filesystem>
GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
@@ -81,7 +81,7 @@ GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id)
void GuildSettingsInfoPane::FetchGuildIcon(const GuildData &guild) {
m_guild_icon.property_pixbuf() = Abaddon::Get().GetImageManager().GetPlaceholder(32);
if (guild.HasIcon()) {
- if (Abaddon::Get().GetSettings().GetShowAnimations() && guild.HasAnimatedIcon()) {
+ if (Abaddon::Get().GetSettings().ShowAnimations && guild.HasAnimatedIcon()) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pixbuf) {
m_guild_icon.property_pixbuf_animation() = pixbuf;
};
diff --git a/windows/guildsettings/infopane.hpp b/src/windows/guildsettings/infopane.hpp
index 829fa3b..8a7e6a2 100644
--- a/windows/guildsettings/infopane.hpp
+++ b/src/windows/guildsettings/infopane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/guild.hpp"
+#include "discord/guild.hpp"
class GuildSettingsInfoPane : public Gtk::Grid {
public:
diff --git a/windows/guildsettings/invitespane.cpp b/src/windows/guildsettings/invitespane.cpp
index 20f5258..bec4784 100644
--- a/windows/guildsettings/invitespane.cpp
+++ b/src/windows/guildsettings/invitespane.cpp
@@ -1,5 +1,5 @@
#include "invitespane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
GuildSettingsInvitesPane::GuildSettingsInvitesPane(Snowflake id)
: GuildID(id)
diff --git a/windows/guildsettings/invitespane.hpp b/src/windows/guildsettings/invitespane.hpp
index 577e195..5268d68 100644
--- a/windows/guildsettings/invitespane.hpp
+++ b/src/windows/guildsettings/invitespane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/objects.hpp"
+#include "discord/objects.hpp"
class GuildSettingsInvitesPane : public Gtk::ScrolledWindow {
public:
diff --git a/windows/guildsettings/memberspane.cpp b/src/windows/guildsettings/memberspane.cpp
index e5fcce5..bda92b4 100644
--- a/windows/guildsettings/memberspane.cpp
+++ b/src/windows/guildsettings/memberspane.cpp
@@ -1,5 +1,5 @@
#include "memberspane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
GuildSettingsMembersPane::GuildSettingsMembersPane(Snowflake id)
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
@@ -99,7 +99,7 @@ GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guil
auto &discord = Abaddon::Get().GetDiscordClient();
- if (member.User->HasAnimatedAvatar() && Abaddon::Get().GetSettings().GetShowAnimations())
+ if (member.User->HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations)
m_avatar.SetURL(member.User->GetAvatarURL("gif", "32"));
else
m_avatar.SetURL(member.User->GetAvatarURL("png", "32"));
@@ -113,8 +113,7 @@ GuildSettingsMembersListItem::GuildSettingsMembersListItem(const GuildData &guil
discord.signal_guild_member_update().connect(sigc::track_obj(member_update_cb, *this));
UpdateColor();
- static bool crown = Abaddon::Get().GetSettings().GetShowOwnerCrown();
- if (crown && guild.OwnerID == member.User->ID) {
+ if (Abaddon::Get().GetSettings().ShowOwnerCrown && guild.OwnerID == member.User->ID) {
try {
const static auto crown_path = Abaddon::GetResPath("/crown.png");
auto pixbuf = Gdk::Pixbuf::create_from_file(crown_path, 12, 12);
@@ -239,9 +238,10 @@ GuildSettingsMembersPaneRoles::GuildSettingsMembersPaneRoles(Snowflake guild_id)
discord.signal_role_delete().connect(sigc::mem_fun(*this, &GuildSettingsMembersPaneRoles::OnRoleDelete));
const auto guild = *discord.GetGuild(guild_id);
- const auto roles = guild.FetchRoles();
- for (const auto &role : roles) {
- CreateRow(can_modify, role, guild.OwnerID == self_id);
+ if (guild.Roles.has_value()) {
+ for (const auto &role : *guild.Roles) {
+ CreateRow(can_modify, role, guild.OwnerID == self_id);
+ }
}
m_list.set_sort_func([this](Gtk::ListBoxRow *a, Gtk::ListBoxRow *b) -> int {
diff --git a/windows/guildsettings/memberspane.hpp b/src/windows/guildsettings/memberspane.hpp
index 7b221b2..01398da 100644
--- a/windows/guildsettings/memberspane.hpp
+++ b/src/windows/guildsettings/memberspane.hpp
@@ -1,9 +1,9 @@
#pragma once
#include <unordered_set>
#include <gtkmm.h>
-#include "../../discord/member.hpp"
-#include "../../discord/guild.hpp"
-#include "../../components/lazyimage.hpp"
+#include "discord/member.hpp"
+#include "discord/guild.hpp"
+#include "components/lazyimage.hpp"
class GuildSettingsMembersPaneRolesItem : public Gtk::ListBoxRow {
public:
diff --git a/windows/guildsettings/rolespane.cpp b/src/windows/guildsettings/rolespane.cpp
index b2f5b35..3567e95 100644
--- a/windows/guildsettings/rolespane.cpp
+++ b/src/windows/guildsettings/rolespane.cpp
@@ -1,5 +1,5 @@
#include "rolespane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
GuildSettingsRolesPane::GuildSettingsRolesPane(Snowflake id)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
@@ -79,19 +79,20 @@ GuildSettingsRolesPaneRoles::GuildSettingsRolesPaneRoles(Snowflake guild_id)
discord.signal_role_delete().connect(sigc::mem_fun(*this, &GuildSettingsRolesPaneRoles::OnRoleDelete));
const auto guild = *discord.GetGuild(GuildID);
- const auto roles = guild.FetchRoles();
const bool can_modify = discord.HasGuildPermission(discord.GetUserData().ID, GuildID, Permission::MANAGE_ROLES);
- for (const auto &role : roles) {
- auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
- row->drag_source_set(g_target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
- row->set_margin_start(5);
- row->set_halign(Gtk::ALIGN_FILL);
- row->show();
- m_rows[role.ID] = row;
- if (can_modify)
- m_list.add_draggable(row);
- else
- m_list.add(*row);
+ if (guild.Roles.has_value()) {
+ for (const auto &role : *guild.Roles) {
+ auto *row = Gtk::manage(new GuildSettingsRolesPaneRolesListItem(guild, role));
+ row->drag_source_set(g_target_entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
+ row->set_margin_start(5);
+ row->set_halign(Gtk::ALIGN_FILL);
+ row->show();
+ m_rows[role.ID] = row;
+ if (can_modify)
+ m_list.add_draggable(row);
+ else
+ m_list.add(*row);
+ }
}
m_list.set_sort_func([this](Gtk::ListBoxRow *rowa_, Gtk::ListBoxRow *rowb_) -> int {
diff --git a/windows/guildsettings/rolespane.hpp b/src/windows/guildsettings/rolespane.hpp
index e5e342a..2999f32 100644
--- a/windows/guildsettings/rolespane.hpp
+++ b/src/windows/guildsettings/rolespane.hpp
@@ -1,8 +1,8 @@
#pragma once
#include <gtkmm.h>
#include <unordered_map>
-#include "../../discord/guild.hpp"
-#include "../../components/draglistbox.hpp"
+#include "discord/guild.hpp"
+#include "components/draglistbox.hpp"
class GuildSettingsRolesPaneRolesListItem : public Gtk::ListBoxRow {
public:
diff --git a/windows/guildsettingswindow.cpp b/src/windows/guildsettingswindow.cpp
index bf0160b..1e3395d 100644
--- a/windows/guildsettingswindow.cpp
+++ b/src/windows/guildsettingswindow.cpp
@@ -1,5 +1,5 @@
#include "guildsettingswindow.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
GuildSettingsWindow::GuildSettingsWindow(Snowflake id)
: m_main(Gtk::ORIENTATION_VERTICAL)
diff --git a/windows/guildsettingswindow.hpp b/src/windows/guildsettingswindow.hpp
index 7447840..b591640 100644
--- a/windows/guildsettingswindow.hpp
+++ b/src/windows/guildsettingswindow.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/snowflake.hpp"
+#include "discord/snowflake.hpp"
#include "guildsettings/infopane.hpp"
#include "guildsettings/banspane.hpp"
#include "guildsettings/invitespane.hpp"
diff --git a/windows/mainwindow.cpp b/src/windows/mainwindow.cpp
index 61ebbfa..659107a 100644
--- a/windows/mainwindow.cpp
+++ b/src/windows/mainwindow.cpp
@@ -1,5 +1,5 @@
#include "mainwindow.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
MainWindow::MainWindow()
: m_main_box(Gtk::ORIENTATION_VERTICAL)
@@ -228,10 +228,12 @@ void MainWindow::UpdateChatReactionRemove(Snowflake id, const Glib::ustring &par
void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
auto &discord = Abaddon::Get().GetDiscordClient();
auto channel_id = GetChatActiveChannel();
- auto channel = discord.GetChannel(channel_id);
m_menu_discord_add_recipient.set_visible(false);
- if (channel.has_value() && channel->GetDMRecipients().size() + 1 < 10)
- m_menu_discord_add_recipient.set_visible(channel->Type == ChannelType::GROUP_DM);
+ if (channel_id.IsValid()) {
+ auto channel = discord.GetChannel(channel_id);
+ if (channel.has_value() && channel->GetDMRecipients().size() + 1 < 10)
+ m_menu_discord_add_recipient.set_visible(channel->Type == ChannelType::GROUP_DM);
+ }
const bool discord_active = Abaddon::Get().GetDiscordClient().IsStarted();
@@ -246,12 +248,14 @@ void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const
void MainWindow::OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) {
m_menu_view_friends.set_sensitive(Abaddon::Get().GetDiscordClient().IsStarted());
auto channel_id = GetChatActiveChannel();
- auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id);
m_menu_view_pins.set_sensitive(false);
m_menu_view_threads.set_sensitive(false);
- if (channel.has_value()) {
- m_menu_view_threads.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->IsThread());
- m_menu_view_pins.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM || channel->IsThread());
+ if (channel_id.IsValid()) {
+ auto channel = Abaddon::Get().GetDiscordClient().GetChannel(channel_id);
+ if (channel.has_value()) {
+ m_menu_view_threads.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->IsThread());
+ m_menu_view_pins.set_sensitive(channel->Type == ChannelType::GUILD_TEXT || channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM || channel->IsThread());
+ }
}
}
diff --git a/windows/mainwindow.hpp b/src/windows/mainwindow.hpp
index 3b41d16..df1c968 100644
--- a/windows/mainwindow.hpp
+++ b/src/windows/mainwindow.hpp
@@ -1,8 +1,8 @@
#pragma once
-#include "../components/channels.hpp"
-#include "../components/chatwindow.hpp"
-#include "../components/memberlist.hpp"
-#include "../components/friendslist.hpp"
+#include "components/channels.hpp"
+#include "components/chatwindow.hpp"
+#include "components/memberlist.hpp"
+#include "components/friendslist.hpp"
#include <gtkmm.h>
class MainWindow : public Gtk::Window {
diff --git a/windows/pinnedwindow.cpp b/src/windows/pinnedwindow.cpp
index 2e9b6fd..a5484e3 100644
--- a/windows/pinnedwindow.cpp
+++ b/src/windows/pinnedwindow.cpp
@@ -1,5 +1,5 @@
#include "pinnedwindow.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
PinnedWindow::PinnedWindow(const ChannelData &data)
: ChannelID(data.ID) {
diff --git a/windows/pinnedwindow.hpp b/src/windows/pinnedwindow.hpp
index 56461a5..cf2ec3c 100644
--- a/windows/pinnedwindow.hpp
+++ b/src/windows/pinnedwindow.hpp
@@ -1,9 +1,9 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/errors.hpp"
-#include "../discord/channel.hpp"
-#include "../discord/message.hpp"
-#include "../components/chatlist.hpp"
+#include "discord/errors.hpp"
+#include "discord/channel.hpp"
+#include "discord/message.hpp"
+#include "components/chatlist.hpp"
class PinnedWindow : public Gtk::Window {
public:
diff --git a/windows/profile/mutualfriendspane.cpp b/src/windows/profile/mutualfriendspane.cpp
index 5e6120a..ca36e1d 100644
--- a/windows/profile/mutualfriendspane.cpp
+++ b/src/windows/profile/mutualfriendspane.cpp
@@ -1,5 +1,5 @@
#include "mutualfriendspane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
MutualFriendItem::MutualFriendItem(const UserData &user)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) {
@@ -9,10 +9,9 @@ MutualFriendItem::MutualFriendItem(const UserData &user)
m_avatar.set_margin_end(10);
- const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
auto &img = Abaddon::Get().GetImageManager();
m_avatar.property_pixbuf() = img.GetPlaceholder(24);
- if (user.HasAnimatedAvatar() && show_animations) {
+ if (user.HasAnimatedAvatar() && Abaddon::Get().GetSettings().ShowAnimations) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
m_avatar.property_pixbuf_animation() = pb;
};
diff --git a/windows/profile/mutualfriendspane.hpp b/src/windows/profile/mutualfriendspane.hpp
index 764dee9..ef41aa6 100644
--- a/windows/profile/mutualfriendspane.hpp
+++ b/src/windows/profile/mutualfriendspane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/objects.hpp"
+#include "discord/objects.hpp"
class MutualFriendItem : public Gtk::Box {
public:
diff --git a/windows/profile/mutualguildspane.cpp b/src/windows/profile/mutualguildspane.cpp
index f7e70f0..6c14fc4 100644
--- a/windows/profile/mutualguildspane.cpp
+++ b/src/windows/profile/mutualguildspane.cpp
@@ -1,5 +1,5 @@
#include "mutualguildspane.hpp"
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
MutualGuildItem::MutualGuildItem(const MutualGuildData &guild)
: Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)
@@ -13,11 +13,10 @@ MutualGuildItem::MutualGuildItem(const MutualGuildData &guild)
// discord will return info (id + nick) for "deleted" guilds from this endpoint. strange !
const auto data = Abaddon::Get().GetDiscordClient().GetGuild(guild.ID);
if (data.has_value()) {
- const auto show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
auto &img = Abaddon::Get().GetImageManager();
m_icon.property_pixbuf() = img.GetPlaceholder(24);
if (data->HasIcon()) {
- if (data->HasAnimatedIcon() && show_animations) {
+ if (data->HasAnimatedIcon() && Abaddon::Get().GetSettings().ShowAnimations) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
m_icon.property_pixbuf_animation() = pb;
};
diff --git a/windows/profile/mutualguildspane.hpp b/src/windows/profile/mutualguildspane.hpp
index 6afdb07..9bdd97e 100644
--- a/windows/profile/mutualguildspane.hpp
+++ b/src/windows/profile/mutualguildspane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/objects.hpp"
+#include "discord/objects.hpp"
class MutualGuildItem : public Gtk::Box {
public:
diff --git a/windows/profile/userinfopane.cpp b/src/windows/profile/userinfopane.cpp
index bfeb4ed..a95a14c 100644
--- a/windows/profile/userinfopane.cpp
+++ b/src/windows/profile/userinfopane.cpp
@@ -1,6 +1,6 @@
#include "userinfopane.hpp"
#include <unordered_set>
-#include "../../abaddon.hpp"
+#include "abaddon.hpp"
ConnectionItem::ConnectionItem(const ConnectionData &conn)
: m_box(Gtk::ORIENTATION_HORIZONTAL)
diff --git a/windows/profile/userinfopane.hpp b/src/windows/profile/userinfopane.hpp
index b29cb76..90a4d55 100644
--- a/windows/profile/userinfopane.hpp
+++ b/src/windows/profile/userinfopane.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../../discord/objects.hpp"
+#include "discord/objects.hpp"
class ConnectionItem : public Gtk::EventBox {
public:
diff --git a/windows/profilewindow.cpp b/src/windows/profilewindow.cpp
index f4bc3c9..9d93564 100644
--- a/windows/profilewindow.cpp
+++ b/src/windows/profilewindow.cpp
@@ -1,5 +1,5 @@
#include "profilewindow.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
ProfileWindow::ProfileWindow(Snowflake user_id)
: ID(user_id)
@@ -44,7 +44,6 @@ ProfileWindow::ProfileWindow(Snowflake user_id)
return false;
});
- static const bool show_animations = Abaddon::Get().GetSettings().GetShowAnimations();
auto &img = Abaddon::Get().GetImageManager();
m_avatar.property_pixbuf() = img.GetPlaceholder(64);
auto icon_cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
@@ -52,7 +51,7 @@ ProfileWindow::ProfileWindow(Snowflake user_id)
};
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(icon_cb, *this));
- if (show_animations && user.HasAnimatedAvatar()) {
+ if (Abaddon::Get().GetSettings().ShowAnimations && user.HasAnimatedAvatar()) {
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
m_avatar.property_pixbuf_animation() = pb;
};
diff --git a/windows/profilewindow.hpp b/src/windows/profilewindow.hpp
index 044c128..3d8199b 100644
--- a/windows/profilewindow.hpp
+++ b/src/windows/profilewindow.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/snowflake.hpp"
+#include "discord/snowflake.hpp"
#include "profile/userinfopane.hpp"
#include "profile/mutualguildspane.hpp"
#include "profile/mutualfriendspane.hpp"
diff --git a/windows/threadswindow.cpp b/src/windows/threadswindow.cpp
index b4cbac7..c18819b 100644
--- a/windows/threadswindow.cpp
+++ b/src/windows/threadswindow.cpp
@@ -1,5 +1,5 @@
#include "threadswindow.hpp"
-#include "../abaddon.hpp"
+#include "abaddon.hpp"
ThreadsWindow::ThreadsWindow(const ChannelData &channel)
: m_channel_id(channel.ID)
@@ -129,14 +129,15 @@ ArchivedThreadsList::ArchivedThreadsList(const ChannelData &channel, const Gtk::
return false;
});
- Abaddon::Get().GetDiscordClient().GetArchivedPublicThreads(channel.ID, sigc::mem_fun(*this, &ArchivedThreadsList::OnPublicFetched));
+ Abaddon::Get().GetDiscordClient().GetArchivedPublicThreads(channel.ID, sigc::mem_fun(*this, &ArchivedThreadsList::OnThreadsFetched));
+ Abaddon::Get().GetDiscordClient().GetArchivedPrivateThreads(channel.ID, sigc::mem_fun(*this, &ArchivedThreadsList::OnThreadsFetched));
}
void ArchivedThreadsList::InvalidateFilter() {
m_list.invalidate_filter();
}
-void ArchivedThreadsList::OnPublicFetched(DiscordError code, const ArchivedThreadsResponseData &data) {
+void ArchivedThreadsList::OnThreadsFetched(DiscordError code, const ArchivedThreadsResponseData &data) {
for (const auto &thread : data.Threads) {
auto row = Gtk::manage(new ThreadListRow(thread));
row->show();
diff --git a/windows/threadswindow.hpp b/src/windows/threadswindow.hpp
index 28d9c7f..ebcbd13 100644
--- a/windows/threadswindow.hpp
+++ b/src/windows/threadswindow.hpp
@@ -1,6 +1,6 @@
#pragma once
#include <gtkmm.h>
-#include "../discord/objects.hpp"
+#include "discord/objects.hpp"
class ActiveThreadsList : public Gtk::ScrolledWindow {
public:
@@ -27,7 +27,7 @@ public:
private:
Gtk::ListBox m_list;
- void OnPublicFetched(DiscordError code, const ArchivedThreadsResponseData &data);
+ void OnThreadsFetched(DiscordError code, const ArchivedThreadsResponseData &data);
using type_signal_thread_open = sigc::signal<void, Snowflake>;
type_signal_thread_open m_signal_thread_open;
diff --git a/thirdparty/IXWebSocket b/subprojects/ixwebsocket
-Subproject e66437b56089be46886084ff3930f352b6d3d90
+Subproject e66437b56089be46886084ff3930f352b6d3d90
diff --git a/thirdparty/simpleini b/thirdparty/simpleini
deleted file mode 160000
-Subproject 67156f64b3447ce1eb81d6be44d29132fb49b70