From 41d80af128cc74e538a2f5d2474ca49febc52c35 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 25 Dec 2021 02:37:31 -0500 Subject: mark more channels as unread properly --- src/discord/discord.cpp | 21 +++++++++++++++++++++ src/discord/snowflake.cpp | 15 ++++++++++++--- src/discord/snowflake.hpp | 3 ++- src/util.cpp | 29 +++++++++++++++++++++++++++++ src/util.hpp | 2 ++ 5 files changed, 66 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 1e9746e..98cab9a 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -2253,6 +2253,7 @@ void DiscordClient::StoreMessageData(Message &msg) { // some notes for myself // a read channel is determined by checking if the channel object's last message id is equal to the read state's last message id +// channels without entries are also unread // here the absence of an entry in m_unread indicates a read channel and the value is only the mention count since the message doesnt matter // no entry.id cannot be a guild even though sometimes it looks like it void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { @@ -2270,6 +2271,26 @@ void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { if (it->second > entry.LastMessageID) m_unread[entry.ID] = entry.MentionCount; } + + // channels that arent in the read state are considered unread + for (const auto &guild : data.Guilds) { + if (!guild.JoinedAt.has_value()) continue; // doubt this can happen but whatever + const auto joined_at = Snowflake::FromISO8601(*guild.JoinedAt); + for (const auto &channel : *guild.Channels) { + if (channel.LastMessageID.has_value()) { + // unread messages from before you joined dont count as unread + if (*channel.LastMessageID < joined_at) continue; + if (std::find_if(data.ReadState.Entries.begin(), data.ReadState.Entries.end(), [id = channel.ID](const ReadStateEntry &e) { + return e.ID == id; + }) == data.ReadState.Entries.end()) { + // cant be unread if u cant even see the channel + // better to check here since HasChannelPermission hits the store + if (HasChannelPermission(GetUserData().ID, channel.ID, Permission::VIEW_CHANNEL)) + m_unread[channel.ID] = 0; + } + } + } + } } void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { diff --git a/src/discord/snowflake.cpp b/src/discord/snowflake.cpp index cea9153..32f9f4f 100644 --- a/src/discord/snowflake.cpp +++ b/src/discord/snowflake.cpp @@ -1,7 +1,8 @@ #include "snowflake.hpp" +#include "util.hpp" +#include #include #include -#include constexpr static uint64_t DiscordEpochSeconds = 1420070400; @@ -14,13 +15,13 @@ Snowflake::Snowflake(uint64_t n) : m_num(n) {} Snowflake::Snowflake(const std::string &str) { - if (str.size()) + if (!str.empty()) m_num = std::stoull(str); else m_num = Invalid; } Snowflake::Snowflake(const Glib::ustring &str) { - if (str.size()) + if (!str.empty()) m_num = std::strtoull(str.c_str(), nullptr, 10); else m_num = Invalid; @@ -38,6 +39,14 @@ Snowflake Snowflake::FromNow() { return snowflake; } +Snowflake Snowflake::FromISO8601(std::string_view ts) { + int yr, mon, day, hr, min, sec, tzhr, tzmin; + float milli; + std::sscanf(ts.data(), "%d-%d-%dT%d:%d:%d%f+%d:%d", + &yr, &mon, &day, &hr, &min, &sec, &milli, &tzhr, &tzmin); + return SecondsInterval * (util::TimeToEpoch(yr, mon, day, hr, min, sec) - DiscordEpochSeconds) + static_cast(milli * static_cast(SecondsInterval)); +} + bool Snowflake::IsValid() const { return m_num != Invalid; } diff --git a/src/discord/snowflake.hpp b/src/discord/snowflake.hpp index 0b79723..f2da5d1 100644 --- a/src/discord/snowflake.hpp +++ b/src/discord/snowflake.hpp @@ -10,6 +10,7 @@ struct Snowflake { Snowflake(const Glib::ustring &str); static Snowflake FromNow(); // not thread safe + static Snowflake FromISO8601(std::string_view ts); bool IsValid() const; std::string GetLocalTimestamp() const; @@ -26,7 +27,7 @@ struct Snowflake { return m_num; } - const static Snowflake Invalid; // makes sense to me + const static Snowflake Invalid; // makes sense to me const static uint64_t SecondsInterval = 4194304000ULL; // the "difference" between two snowflakes one second apart friend void from_json(const nlohmann::json &j, Snowflake &s); diff --git a/src/util.cpp b/src/util.cpp index 34ca6d4..6796a3f 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,4 +1,5 @@ #include "util.hpp" +#include #include Semaphore::Semaphore(int count) @@ -215,3 +216,31 @@ bool util::IsFile(std::string_view path) { if (ec) return false; return status.type() == std::filesystem::file_type::regular; } + +constexpr bool IsLeapYear(int year) { + if (year % 4 != 0) return false; + if (year % 100 != 0) return true; + return (year % 400) == 0; +} + +constexpr static int SecsPerMinute = 60; +constexpr static int SecsPerHour = 3600; +constexpr static int SecsPerDay = 86400; +constexpr static std::array DaysOfMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +// may god smite whoever is responsible for the absolutely abominable api that is C time functions +// i shouldnt have to write this. mktime ALMOST works but it adds the current timezone offset. WHY??? +uint64_t util::TimeToEpoch(int year, int month, int day, int hour, int minute, int seconds) { + uint64_t secs = 0; + for (int y = 1970; y < year; ++y) + secs += (IsLeapYear(y) ? 366 : 365) * SecsPerDay; + for (int m = 1; m < month; ++m) { + secs += DaysOfMonth[m - 1] * SecsPerDay; + if (m == 2 && IsLeapYear(year)) secs += SecsPerDay; + } + secs += (day - 1) * SecsPerDay; + secs += hour * SecsPerHour; + secs += minute * SecsPerMinute; + secs += seconds; + return secs; +} diff --git a/src/util.hpp b/src/util.hpp index f51a917..aa87301 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -27,6 +27,8 @@ struct is_optional<::std::optional> : ::std::true_type {}; bool IsFolder(std::string_view path); bool IsFile(std::string_view path); + +uint64_t TimeToEpoch(int year, int month, int day, int hour, int minute, int seconds); } // namespace util class Semaphore { -- cgit v1.2.3