Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: load Twitch emotes from Helix #5239

Merged
merged 22 commits into from
Sep 1, 2024

Conversation

Nerixyz
Copy link
Contributor

@Nerixyz Nerixyz commented Mar 8, 2024

It's probably not a fix, but for a user it's a fix.

This PR refactors how Twitch emotes are loaded - using the new Helix API.

Although the API returns decent data, it's slow. For my user (two subscriptions), I need to make 28 requests to get all emotes for one channel. One request takes about 100-250ms.

To help reduce latency, the emotes are fetched once for the user (by leaving broadcaster_id empty) and for each channel, the follow-status is checked and follower emotes are loaded. This requires (user:read:follows, Chatterino/website#527).

Related twitchdev issues:


@Nerixyz Nerixyz marked this pull request as draft March 8, 2024 19:37
@Felanbird
Copy link
Collaborator

Felanbird commented Mar 8, 2024

With this approach we're going to need to use owner_id over emote_set_id, at least for a specific list of channel IDs, otherwise the global tab is going to be even worse than before.

image

image

@Felanbird
Copy link
Collaborator

Felanbird commented Mar 19, 2024

notes:

a cavalcade of funni errors

image

f5-ing a single channel will label the `Twitch` section of global emotes incorrectly:

loaded naturally
image
f5 #󠀀1
image
f5 #󠀀2
image

emote menu can populate gif emotes twice in some channels (maybe specifically an issue with perma subs)

image

this does not effect the : menu
image

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Mar 19, 2024

a cavalcade of funni errors

I'm hoping for Twitch to utilize the pages better. But we'll still need some rate-limiting.

f5-ing a single channel will label the Twitch section of global emotes incorrectly:

Fixed by grouping everything to Twitch (no owner-id).

emote menu can populate gif emotes twice in some channels (maybe specifically an issue with perma subs)

Seems to be twitchdev/issues#922 - I'm hoping this gets fixed - having yet another map to detect duplicates seems pointless. Btw, it's only in the popup.

@Nerixyz Nerixyz force-pushed the fix/twitch-emotes branch 2 times, most recently from 71c1eea to 4b3f78a Compare March 19, 2024 12:42
@pajlada
Copy link
Member

pajlada commented Apr 1, 2024

#5289 - should make sure the emotes listed as well ordered

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
@Nerixyz
Copy link
Contributor Author

Nerixyz commented Aug 4, 2024

So this is functionally "complete" but still really slow. It tries to cache emote sets for channels that haven't resolved yet. But from my testing, all channels resolved equally slow, so there wouldn't be any benefit.

I'm waiting for Twitch to reply to the issues mentioned in the description and for them to potentially use better page sizes (some are tiny, costing a few RTTs - this adds up).

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
src/providers/twitch/TwitchAccount.hpp Outdated Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
@occluder
Copy link

occluder commented Aug 4, 2024

Although the API returns decent data, it's slow. For my user (two subscriptions), I need to make 28 requests to get all emotes for one channel. One request takes about 100-250ms. Now imagine how long this would take if a user has 50 (or 100) channels open and more subscriptions. I don't think it's my implementation (this would generate a report though the slow HTTP handler alerts).

Are you able to retest this? It doesn't seem right; I have 4 active subscriptions right now and it fetched everything in 6 calls (no broadcaster_id specified)

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Aug 5, 2024

Are you able to retest this? It doesn't seem right; I have 4 active subscriptions right now and it fetched everything in 6 calls (no broadcaster_id specified)

It seems weird that no broadcaster_id was specified...

I've added some additional logging for each request and for the total (I'll probably remove this if this gets merged). For my user, it's actually 28 requests. They're probably from event emotes that have accumulated throughout the years (as you can see, the size of each page is small).

Logs
chatterino.twitch: [TwitchChannel "nerixyz"] Loading Twitch emotes - userID: "129546453", broadcasterID: "129546453", manualRefresh: false
chatterino.twitch: [TwitchChannel "nymn"] Loading Twitch emotes - userID: "129546453", broadcasterID: "62300805", manualRefresh: false
chatterino.twitch: [TwitchChannel "pajlada"] Loading Twitch emotes - userID: "129546453", broadcasterID: "11148817", manualRefresh: false
chatterino.twitch: [TwitchChannel "uint128"] Loading Twitch emotes - userID: "129546453", broadcasterID: "489584266", manualRefresh: false
chatterino.twitch: [TwitchChannel "nerixyz"] Got 16 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 16 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 10 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 10 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 10 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 10 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 47 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 47 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 47 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 47 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 4 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 4 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 4 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 4 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 46 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 46 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 46 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 46 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 21 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 9 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 9 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 9 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 9 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Got 275 more emote(s)
chatterino.twitch: [TwitchChannel "nymn"] Loaded 533 Twitch emotes (28 requests)
chatterino.twitch: [TwitchChannel "nerixyz"] Got 275 more emote(s)
chatterino.twitch: [TwitchChannel "nerixyz"] Loaded 528 Twitch emotes (28 requests)
chatterino.twitch: [TwitchChannel "uint128"] Got 5 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Got 275 more emote(s)
chatterino.twitch: [TwitchChannel "pajlada"] Loaded 528 Twitch emotes (28 requests)
chatterino.twitch: [TwitchChannel "uint128"] Got 275 more emote(s)
chatterino.twitch: [TwitchChannel "uint128"] Loaded 528 Twitch emotes (28 requests)

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
src/providers/twitch/TwitchAccount.hpp Outdated Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
@iProdigy
Copy link
Contributor

iProdigy commented Aug 5, 2024

It seems weird that no broadcaster_id was specified...

Yeah we can paginate once without broadcaster_id, but we need to specify broadcaster_id for the channels we have joined but are not subscribed (can leverage userstate for this logic)

@occluder
Copy link

occluder commented Aug 5, 2024

It seems weird that no broadcaster_id was specified...

Yeah we can paginate once without broadcaster_id, but we need to specify broadcaster_id for the channels we have joined but are not subscribed (can leverage userstate for this logic)

I believe 'Get Channel Emotes' would work better for this, as it requires no pagination to get all emotes

@iProdigy
Copy link
Contributor

iProdigy commented Aug 5, 2024

I believe 'Get Channel Emotes' would work better for this, as it requires no pagination to get all emotes

good point, can use Get Channel Emotes, just have to do separate api call to check if user is following the channel

@Nerixyz
Copy link
Contributor Author

Nerixyz commented Aug 5, 2024

good point, can use Get Channel Emotes, just have to do separate api call to check if user is following the channel

I don't like this, but it's probably the best idea looking at the request count. My main concern is that Twitch could add more emotes that are local, and Get User Emotes would handle that just fine. There's a uservoice to add a parameter that would only return local emotes, which would be the ideal API.

This changed a bunch of stuff but I'm too tired to look at it now maybe tomorrow. Also in total there are a few unrelated changes. I would love something like Rust's async-await here but I don't have it so the code looks really ugly (blame shifting, I'll refactor it later hopefully).
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
@Nerixyz Nerixyz marked this pull request as ready for review August 6, 2024 13:03
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
src/common/Aliases.hpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
tests/src/CancellationToken.cpp Show resolved Hide resolved
Copy link
Member

@pajlada pajlada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a full review, overall looks good. Less changes required for this than I would have imagined.
I've pushed some documentation changes as way for me to say: "I think this is how it works"

Some emotes seem to be duplicated for me, might have to do with t3 sub
image

They only show up once in the emote completion popup & the emote tab complketino

src/controllers/completion/sources/EmoteSource.cpp Outdated Show resolved Hide resolved
CancellationToken token(false);
ASSERT_FALSE(token.isCancelled());
{
ScopedCancellationToken scoped(std::move(token));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong:
Since ScopedCancellationToken has no accessors on its own, moving a CancellationToken into a ScopedCancellationToken means no one can check its state, I feel like unless ScopedCancellationToken has its own accessors for its backingToken_, the move ctor & move assignment operator should be deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It takes the CancellationToken by value now. But generally, the idea is that using a move, you can avoid one copy of the shared_ptr. It's not really visible in the constructor as much but more in the assignment operator:

CancellationToken token(false);
sendRequest("/foo", token /* copied */);
this->myToken = std::move(token); // avoid any copy of the shared_ptr

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/EmptyApplication.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
mocks/include/mocks/Helix.hpp Show resolved Hide resolved
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

src/util/Expected.hpp Outdated Show resolved Hide resolved
src/util/Expected.hpp Outdated Show resolved Hide resolved
Copy link
Member

@pajlada pajlada left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On testaccount_420, where I only have one t3 sub to pajlada:
Sub emotes correctly show up as sub emotes.
Global Twitch emotes correctly show up as Global Twitch emotes.
pajlada's follower emotes wrongly show up as Global Twitch emotes

Loading 342 emotes took 3 requests

On pajlada, where I have a bunch of various subs:
Bit emotes show up correctly
Sub emotes show up correctly
Follower emotes show up as global emotes if you're subbed

When opening the emote popup in a channel that hasn't been fully joined yet (i.e. the room ID hasn't loaded), global emotes show up as sub emotes before things have

Loading 1068 emotes took 46 requests

@pajlada pajlada merged commit 820aa12 into Chatterino:master Sep 1, 2024
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants