Skip to content

cocorum.chatapi

The primary use from this module is the ChatAPI class, a wrapper for Rumble's internal chat API. When passed credentials, it currently automatically creates an internal instance of cocorum.servicephp.ServicePHP() to log in with, and includes links to the chat-related methods of ServicePHP() (muting users for example). All other classes are supporting sub-classes.

Internal chat API client

Interface with the Rumble chat API to send and receive messages, etc.

Copyright 2025 Wilbur Jaywright.

This file is part of Cocorum.

Cocorum is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Cocorum is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with Cocorum. If not, see https://www.gnu.org/licenses/.

S.D.G.

BaseComment

A comment on a Rumble video

Source code in cocorum/basehandles.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class BaseComment:
    """A comment on a Rumble video"""
    def __int__(self):
        """The comment in integer form (its ID)"""
        return self.comment_id

    def __str__(self):
        """The comment as a string (its text)"""
        return self.text

    @property
    def comment_id_b10(self):
        """The base 10 ID of the comment"""
        return self.comment_id

    @property
    def comment_id_b36(self):
        """The base 36 ID of the comment"""
        return utils.base_10_to_36(self.comment_id)

    def __eq__(self, other):
        """Determine if this comment is equal to another.

    Args:
        other (int, str, HTMLComment, APIComment): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        # Check for direct matches first
        if isinstance(other, int):
            return self.comment_id_b10 == other
        if isinstance(other, str):
            return str(self) == other

        # Check for object attributes to match to
        if hasattr(other, "comment_id_b10"):
            return self.comment_id_b10 == other.comment_id_b10

        # Check conversion to integer last
        if hasattr(other, "__int__"):
            return self.comment_id_b10 == int(other)

        return False

    def pin(self, unpin: bool = False):
        """Pin or unpin this comment.

    Args:
        unpin (bool): If true, unpins instead of pinning comment.
        """

        return self.servicephp.comment_pin(self, unpin)

    def delete(self):
        """Delete this comment"""

        return self.servicephp.comment_delete(self)

    def restore(self):
        """Un-delete this comment"""

        return self.servicephp.comment_restore(self)

    def set_rumbles(self, vote: int):
        """Post a like or dislike on this comment.

    Args:
        vote (int): -1, 0, or 1 (0 means clear vote).
        """

        return self.servicephp.rumbles(vote, self, item_type = 2)

comment_id_b10 property

The base 10 ID of the comment

comment_id_b36 property

The base 36 ID of the comment

__eq__(other)

Determine if this comment is equal to another.

Parameters:

Name Type Description Default
other (int, str, HTMLComment, APIComment)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/basehandles.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def __eq__(self, other):
    """Determine if this comment is equal to another.

Args:
    other (int, str, HTMLComment, APIComment): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    # Check for direct matches first
    if isinstance(other, int):
        return self.comment_id_b10 == other
    if isinstance(other, str):
        return str(self) == other

    # Check for object attributes to match to
    if hasattr(other, "comment_id_b10"):
        return self.comment_id_b10 == other.comment_id_b10

    # Check conversion to integer last
    if hasattr(other, "__int__"):
        return self.comment_id_b10 == int(other)

    return False

__int__()

The comment in integer form (its ID)

Source code in cocorum/basehandles.py
62
63
64
def __int__(self):
    """The comment in integer form (its ID)"""
    return self.comment_id

__str__()

The comment as a string (its text)

Source code in cocorum/basehandles.py
66
67
68
def __str__(self):
    """The comment as a string (its text)"""
    return self.text

delete()

Delete this comment

Source code in cocorum/basehandles.py
115
116
117
118
def delete(self):
    """Delete this comment"""

    return self.servicephp.comment_delete(self)

pin(unpin=False)

Pin or unpin this comment.

Parameters:

Name Type Description Default
unpin bool

If true, unpins instead of pinning comment.

False
Source code in cocorum/basehandles.py
106
107
108
109
110
111
112
113
def pin(self, unpin: bool = False):
    """Pin or unpin this comment.

Args:
    unpin (bool): If true, unpins instead of pinning comment.
    """

    return self.servicephp.comment_pin(self, unpin)

restore()

Un-delete this comment

Source code in cocorum/basehandles.py
120
121
122
123
def restore(self):
    """Un-delete this comment"""

    return self.servicephp.comment_restore(self)

set_rumbles(vote)

Post a like or dislike on this comment.

Parameters:

Name Type Description Default
vote int

-1, 0, or 1 (0 means clear vote).

required
Source code in cocorum/basehandles.py
125
126
127
128
129
130
131
132
def set_rumbles(self, vote: int):
    """Post a like or dislike on this comment.

Args:
    vote (int): -1, 0, or 1 (0 means clear vote).
    """

    return self.servicephp.rumbles(vote, self, item_type = 2)

BaseContentVotes

Likes and dislikes on a video or comment

Source code in cocorum/basehandles.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
class BaseContentVotes:
    """Likes and dislikes on a video or comment"""

    def __int__(self):
        """The integer form of the content votes"""
        return self.score

    def __eq__(self, other):
        """Determine if this content votes is equal to another.

    Args:
        other (int, str, HTMLContentVotes): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        # Check for direct matches first
        if isinstance(other, int):
            return self.score == other
        if isinstance(other, str):
            return str(self) == other

        # Check for object attributes to match to
        if hasattr(other, "score"):
            return self.score == other.score

        # Check conversion to integer last
        if hasattr(other, "__int__"):
            return self.score == int(other)

        return False

__eq__(other)

Determine if this content votes is equal to another.

Parameters:

Name Type Description Default
other (int, str, HTMLContentVotes)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/basehandles.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def __eq__(self, other):
    """Determine if this content votes is equal to another.

Args:
    other (int, str, HTMLContentVotes): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    # Check for direct matches first
    if isinstance(other, int):
        return self.score == other
    if isinstance(other, str):
        return str(self) == other

    # Check for object attributes to match to
    if hasattr(other, "score"):
        return self.score == other.score

    # Check conversion to integer last
    if hasattr(other, "__int__"):
        return self.score == int(other)

    return False

__int__()

The integer form of the content votes

Source code in cocorum/basehandles.py
137
138
139
def __int__(self):
    """The integer form of the content votes"""
    return self.score

BasePlaylist

A playlist of Rumble videos

Source code in cocorum/basehandles.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
class BasePlaylist:
    """A playlist of Rumble videos"""

    def __int__(self):
        """The playlist as an integer (it's ID in base 10)"""
        return self.playlist_id_b10

    def __str__(self):
        """The playlist as a string (it's ID in base 64)"""
        return self.playlist_id_b64

    def __eq__(self, other):
        """Determine if this playlist is equal to another.

    Args:
        other (int, str, HTMLPlaylist): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        # Check for direct matches first
        if isinstance(other, int):
            return self.playlist_id_b64 == other
        if isinstance(other, str):
            return str(other) == self.playlist_id_b64

        # # Check for object attributes to match to
        # if hasattr(other, "playlist_id_b10"):
        #     return self.playlist_id_b10 == other.playlist_id_b10

        # # Check conversion to integer last, in case another ID or something happens to match
        # if hasattr(other, "__int__"):
        #     return self.playlist_id_b10 == int(other)

        return False

    @property
    def playlist_id_b64(self):
        """The numeric ID of the playlist in base 64"""
        return self.playlist_id

    @property
    def playlist_id_b10(self):
        """The numeric ID of the playlist in base 10"""
        raise NotImplementedError("See Cocorum issue #22")
        # return utils.base_36_to_10(self.playlist_id)

    def add_video(self, video_id):
        """Add a video to this playlist

    Args:
        video_id (int, str): The numeric ID of the video to add, in base 10 or 36.
        """

        return self.servicephp.playlist_add_video(self.playlist_id, video_id)

    def delete_video(self, video_id):
        """Remove a video from this playlist

    Args:
        video_id (int, str): The numeric ID of the video to remove, in base 10 or 36.
        """

        self.servicephp.playlist_delete_video(self.playlist_id, video_id)

    def edit(self, title: str = None, description: str = None, visibility: str = None, channel_id = None):
        """Edit the details of this playlist. WARNING: The original object will probably be stale after this operation.

    Args:
        title (str): The title of the playlist.
            Defaults to staying the same.
        description (str): Describe the playlist.
            Defaults to staying the same.
        visibility (str): Set to public, unlisted, or private via string.
            Defaults to staying the same.
        channel_id (int | str | None): The ID of the channel to have the playlist under. TODO: Cannot be retrieved!
            Defaults to resetting to None.

    Returns:
        playlist (APIPlaylist): The edit result.
        """

        if title is None:
            title = self.title
        if description is None:
            description = self.description
        if visibility is None:
            visibility = self.visibility
        # if channel_id is False:
        #     channel_id = self.channel_id

        return self.servicephp.playlist_edit(self.playlist_id, title, description, visibility, channel_id)

    def delete(self):
        """Delete this playlist"""

        self.servicephp.playlist_delete(self)

playlist_id_b10 property

The numeric ID of the playlist in base 10

playlist_id_b64 property

The numeric ID of the playlist in base 64

__eq__(other)

Determine if this playlist is equal to another.

Parameters:

Name Type Description Default
other (int, str, HTMLPlaylist)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/basehandles.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
def __eq__(self, other):
    """Determine if this playlist is equal to another.

Args:
    other (int, str, HTMLPlaylist): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    # Check for direct matches first
    if isinstance(other, int):
        return self.playlist_id_b64 == other
    if isinstance(other, str):
        return str(other) == self.playlist_id_b64

    # # Check for object attributes to match to
    # if hasattr(other, "playlist_id_b10"):
    #     return self.playlist_id_b10 == other.playlist_id_b10

    # # Check conversion to integer last, in case another ID or something happens to match
    # if hasattr(other, "__int__"):
    #     return self.playlist_id_b10 == int(other)

    return False

__int__()

The playlist as an integer (it's ID in base 10)

Source code in cocorum/basehandles.py
229
230
231
def __int__(self):
    """The playlist as an integer (it's ID in base 10)"""
    return self.playlist_id_b10

__str__()

The playlist as a string (it's ID in base 64)

Source code in cocorum/basehandles.py
233
234
235
def __str__(self):
    """The playlist as a string (it's ID in base 64)"""
    return self.playlist_id_b64

add_video(video_id)

Add a video to this playlist

Parameters:

Name Type Description Default
video_id (int, str)

The numeric ID of the video to add, in base 10 or 36.

required
Source code in cocorum/basehandles.py
274
275
276
277
278
279
280
281
def add_video(self, video_id):
    """Add a video to this playlist

Args:
    video_id (int, str): The numeric ID of the video to add, in base 10 or 36.
    """

    return self.servicephp.playlist_add_video(self.playlist_id, video_id)

delete()

Delete this playlist

Source code in cocorum/basehandles.py
320
321
322
323
def delete(self):
    """Delete this playlist"""

    self.servicephp.playlist_delete(self)

delete_video(video_id)

Remove a video from this playlist

Parameters:

Name Type Description Default
video_id (int, str)

The numeric ID of the video to remove, in base 10 or 36.

required
Source code in cocorum/basehandles.py
283
284
285
286
287
288
289
290
def delete_video(self, video_id):
    """Remove a video from this playlist

Args:
    video_id (int, str): The numeric ID of the video to remove, in base 10 or 36.
    """

    self.servicephp.playlist_delete_video(self.playlist_id, video_id)

edit(title=None, description=None, visibility=None, channel_id=None)

Edit the details of this playlist. WARNING: The original object will probably be stale after this operation.

Parameters:

Name Type Description Default
title str

The title of the playlist. Defaults to staying the same.

None
description str

Describe the playlist. Defaults to staying the same.

None
visibility str

Set to public, unlisted, or private via string. Defaults to staying the same.

None
channel_id int | str | None

The ID of the channel to have the playlist under. TODO: Cannot be retrieved! Defaults to resetting to None.

None

Returns:

Name Type Description
playlist APIPlaylist

The edit result.

Source code in cocorum/basehandles.py
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
def edit(self, title: str = None, description: str = None, visibility: str = None, channel_id = None):
    """Edit the details of this playlist. WARNING: The original object will probably be stale after this operation.

Args:
    title (str): The title of the playlist.
        Defaults to staying the same.
    description (str): Describe the playlist.
        Defaults to staying the same.
    visibility (str): Set to public, unlisted, or private via string.
        Defaults to staying the same.
    channel_id (int | str | None): The ID of the channel to have the playlist under. TODO: Cannot be retrieved!
        Defaults to resetting to None.

Returns:
    playlist (APIPlaylist): The edit result.
    """

    if title is None:
        title = self.title
    if description is None:
        description = self.description
    if visibility is None:
        visibility = self.visibility
    # if channel_id is False:
    #     channel_id = self.channel_id

    return self.servicephp.playlist_edit(self.playlist_id, title, description, visibility, channel_id)

BaseUser

A Rumble user

Source code in cocorum/basehandles.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
class BaseUser:
    """A Rumble user"""

    def __int__(self):
        """The user as an integer (it's ID in base 10)"""
        return self.user_id_b10

    def __eq__(self, other):
        """Determine if this user is equal to another.

    Args:
        other (int, str, APIUser): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        #Check for direct matches first
        if isinstance(other, int):
            return self.user_id_b10 == other
        if isinstance(other, str):
            return str(other) in (self.user_id_b36, self.username)

        #Check for object attributes to match to
        if hasattr(other, "user_id"):
            return self.user_id_b10 == utils.ensure_b10(other.user_id)

        #Check conversion to integer last, in case another ID or something happens to match
        if hasattr(other, "__int__"):
            return self.user_id_b10 == int(other)

        return False

    @property
    def user_id_b10(self):
        """The numeric ID of the user in base 10"""
        return self.user_id

    @property
    def user_id_b36(self):
        """The numeric ID of the user in base 36"""
        return utils.base_10_to_36(self.user_id)

    def mute(self, duration: int = None, total: bool = False):
        """Mute this user.

    Args:
        duration (int): How long to mute the user in seconds.
            Defaults to infinite.
        total (bool): Wether or not they are muted across all videos.
            Defaults to False, just this video.
            """

        self.servicephp.mute(self, self.username, duration, total)

    def unmute(self):
        """Unmute this user."""
        self.servicephp.unmute(self.username)

user_id_b10 property

The numeric ID of the user in base 10

user_id_b36 property

The numeric ID of the user in base 36

__eq__(other)

Determine if this user is equal to another.

Parameters:

Name Type Description Default
other (int, str, APIUser)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/basehandles.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
def __eq__(self, other):
    """Determine if this user is equal to another.

Args:
    other (int, str, APIUser): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    #Check for direct matches first
    if isinstance(other, int):
        return self.user_id_b10 == other
    if isinstance(other, str):
        return str(other) in (self.user_id_b36, self.username)

    #Check for object attributes to match to
    if hasattr(other, "user_id"):
        return self.user_id_b10 == utils.ensure_b10(other.user_id)

    #Check conversion to integer last, in case another ID or something happens to match
    if hasattr(other, "__int__"):
        return self.user_id_b10 == int(other)

    return False

__int__()

The user as an integer (it's ID in base 10)

Source code in cocorum/basehandles.py
170
171
172
def __int__(self):
    """The user as an integer (it's ID in base 10)"""
    return self.user_id_b10

mute(duration=None, total=False)

Mute this user.

Parameters:

Name Type Description Default
duration int

How long to mute the user in seconds. Defaults to infinite.

None
total bool

Wether or not they are muted across all videos. Defaults to False, just this video.

False
Source code in cocorum/basehandles.py
210
211
212
213
214
215
216
217
218
219
220
def mute(self, duration: int = None, total: bool = False):
    """Mute this user.

Args:
    duration (int): How long to mute the user in seconds.
        Defaults to infinite.
    total (bool): Wether or not they are muted across all videos.
        Defaults to False, just this video.
        """

    self.servicephp.mute(self, self.username, duration, total)

unmute()

Unmute this user.

Source code in cocorum/basehandles.py
222
223
224
def unmute(self):
    """Unmute this user."""
    self.servicephp.unmute(self.username)

BaseUserBadge

A badge on a username

Source code in cocorum/basehandles.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class BaseUserBadge:
    """A badge on a username"""
    def __eq__(self, other):
        """Check if this badge is equal to another.

    Args:
        other (str, HTMLUserBadge): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        # Check if the string is either our slug or our label in any language
        if isinstance(other, str):
            return other in (self.slug, self.label.values())

        # Check if the compared object has the same slug, if it has one
        if hasattr(other, "slug"):
            return self.slug == other.slug

        return False

    def __str__(self):
        """The chat user badge in string form"""
        return self.slug

    @property
    def icon(self):
        """The badge's icon as a bytestring"""
        if not self.__icon:  # We never queried the icon before
            # TODO make the timeout configurable
            response = requests.get(self.icon_url, timeout = static.Delays.request_timeout)
            assert response.status_code == 200, "Status code " + str(response.status_code)

            self.__icon = response.content

        return self.__icon

icon property

The badge's icon as a bytestring

__eq__(other)

Check if this badge is equal to another.

Parameters:

Name Type Description Default
other (str, HTMLUserBadge)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/basehandles.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __eq__(self, other):
    """Check if this badge is equal to another.

Args:
    other (str, HTMLUserBadge): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    # Check if the string is either our slug or our label in any language
    if isinstance(other, str):
        return other in (self.slug, self.label.values())

    # Check if the compared object has the same slug, if it has one
    if hasattr(other, "slug"):
        return self.slug == other.slug

    return False

__str__()

The chat user badge in string form

Source code in cocorum/basehandles.py
44
45
46
def __str__(self):
    """The chat user badge in string form"""
    return self.slug

Channel

Bases: Chatter

A channel in the SSE chat

Source code in cocorum/chatapi.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
class Channel(Chatter):
    """A channel in the SSE chat"""
    def __init__(self, jsondata, chat):
        """A channel in the internal chat API

    Args:
        jsondata (dict): The JSON data block for the channel.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """

        super().__init__(jsondata, chat)

        # Find the user who has this channel
        for user in self.chat.users.values():
            if user.channel_id == self.channel_id or self.channel_id in user.previous_channel_ids:
                self.user = user
                break

    @property
    def is_appearing(self):
        """Is the user of this channel still appearing as it?"""
        return self.user.channel_id == self.channel_id # The user channel_id still matches our own

    @property
    def channel_id(self):
        """The ID of this channel in base 10"""
        return int(self["id"])

    @property
    def channel_id_b10(self):
        """The ID of this channel in base 10"""
        return self.channel_id

    @property
    def channel_id_b36(self):
        """The ID of this channel in base 36"""
        return utils.base_10_to_36(self.channel_id)

    @property
    def user_id(self):
        """The numeric ID of the user of this channel"""
        return self.user.user_id

    @property
    def user_id_b36(self):
        """The numeric ID of the user of this channel in base 36"""
        return self.user.user_id_b36

    @property
    def user_id_b10(self):
        """The numeric ID of the user of this channel in base 10"""
        return self.user.user_id_b10

channel_id property

The ID of this channel in base 10

channel_id_b10 property

The ID of this channel in base 10

channel_id_b36 property

The ID of this channel in base 36

is_appearing property

Is the user of this channel still appearing as it?

user_id property

The numeric ID of the user of this channel

user_id_b10 property

The numeric ID of the user of this channel in base 10

user_id_b36 property

The numeric ID of the user of this channel in base 36

__init__(jsondata, chat)

A channel in the internal chat API

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the channel.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def __init__(self, jsondata, chat):
    """A channel in the internal chat API

Args:
    jsondata (dict): The JSON data block for the channel.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """

    super().__init__(jsondata, chat)

    # Find the user who has this channel
    for user in self.chat.users.values():
        if user.channel_id == self.channel_id or self.channel_id in user.previous_channel_ids:
            self.user = user
            break

ChatAPI

The Rumble internal chat API

Source code in cocorum/chatapi.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
class ChatAPI():
    """The Rumble internal chat API"""
    def __init__(self, stream_id, username: str = None, password: str = None, session = None, history_len = 1000):
        """The Rumble internal chat API

    Args:
        stream_id (int, str): Stream ID in base 10 int or base 36 str.
            WARNING: If a str is passed, this WILL ASSUME BASE 36
            even if only digits are present! Convert to int before passing
            if it is base 10.
        username (str): Username to login with.
            Defaults to no login.
        password (str): Password to log in with.
            Defaults to no login.
        session (str, dict): Session token or cookie dict to authenticate with.
            Defaults to getting new session with username and password.
        history_len (int): Length of message history to store.
            Defaults to 1000.
            """

        self.stream_id = utils.ensure_b36(stream_id)

        self.__mailbox = []  #  A mailbox if you will
        self.__history = []  #  Chat history
        self.history_len = history_len  #  How many messages to store in history
        self.pinned_message = None  #  If a message is pinned, it is assigned to this
        self.users = {}  #  Dictionary of users by user ID
        self.channels = {}  #  Dictionary of channels by channel ID
        self.badges = {}

        # Generate our URLs
        self.sse_url = static.URI.ChatAPI.sse_stream.format(stream_id_b10 = self.stream_id_b10)
        print("SSE stream URL:", self.sse_url)
        self.message_api_url = static.URI.ChatAPI.message.format(stream_id_b10 = self.stream_id_b10)

        #  Connect to SSE stream
        #  Note: We do NOT want this request to have a timeout
        self.response = requests.get(self.sse_url, stream = True, headers = static.RequestHeaders.sse_api)
        self.client = sseclient.SSEClient(self.response)
        self.event_generator = self.client.events()
        self.chat_running = True

        #  If we have session login, use them
        if (username and password) or session:
            self.servicephp = ServicePHP(username, password, session)
            self.scraper = scraping.Scraper(self.servicephp)
        else:
            self.servicephp = None

        #  Parse the init data for the stream (must do AFTER we have servicephp)
        self.parse_init_data(self.__next_event_json())

        #  The last time we sent a message
        self.last_send_time = 0

    def close(self):
        """Close the chat connection"""
        self.response.close()
        self.chat_running = False

    @property
    def session_cookie(self):
        """The session cookie we are logged in with"""
        if self.servicephp:
            return self.servicephp.session_cookie
        return None

    @property
    def history(self):
        """The chat history, trimmed to history_len"""
        return tuple(self.__history)

    def send_message(self, text: str, channel_id: int = None):
        """Send a message in chat.

    Args:
        text (str): The message text.
        channel_id (int): Numeric ID of the channel to use.
            Defaults to None.

    Returns:
        ID (int): The ID of the sent message.
        User (User): Your current chat user information.
        """

        assert self.session_cookie, "Not logged in, cannot send message"
        assert len(text) <= static.Message.max_len, "Mesage is too long"
        curtime = time.time()
        assert self.last_send_time + static.Message.send_cooldown <= curtime, "Sending messages too fast"
        assert utils.options_check(self.message_api_url, "POST"), "Rumble denied options request to post message"
        r = requests.post(
            self.message_api_url,
            cookies = self.session_cookie,
            json = {
                "data": {
                    "request_id": utils.generate_request_id(),
                    "message": {
                        "text": text
                    },
                    "rant": None,
                    "channel_id": channel_id
                    }
                },
            #  headers = static.RequestHeaders.user_agent,
            timeout = static.Delays.request_timeout,
            )

        if r.status_code != 200:
            print("Error: Sending message failed,", r, r.text)
            return

        return int(r.json()["data"]["id"]), User(r.json()["data"]["user"], self)

    def command(self, command_message: str):
        """Send a native chat command

    Args:
        command_message (str): The message you would send to launch this command in chat.

    Returns:
        JSON (dict): The JSON returned by the command.
        """

        assert command_message.startswith(static.Message.command_prefix), "Not a command message"
        r = requests.post(
            static.URI.ChatAPI.command,
            data = {
                "video_id" : self.stream_id_b10,
                "message" : command_message,
                },
            cookies = self.session_cookie,
            headers = static.RequestHeaders.user_agent,
            timeout = static.Delays.request_timeout,
            )
        assert r.status_code == 200, f"Command failed: {r}\n{r.text}"
        return r.json()

    def delete_message(self, message):
        """Delete a message in chat.

    Args:
        message (int | Message): Object which when converted to integer is the target message ID.

    Returns:
        success (bool): Wether the operation succeeded or not.
            NOTE: Method will also print an error message if it failed.
        """

        assert not hasattr(message, "deleted") or not message.deleted, "Message was already deleted"

        assert self.session_cookie, "Not logged in, cannot delete message"
        assert utils.options_check(self.message_api_url + f"/{int(message)}", "DELETE"), "Rumble denied options request to delete message"

        r = requests.delete(
            self.message_api_url + f"/{int(message)}",
            cookies = self.session_cookie,
            #  headers = static.RequestHeaders.user_agent,
            timeout = static.Delays.request_timeout,
            )

        if r.status_code != 200:
            print("Error: Deleting message failed,", r, r.content.decode(static.Misc.text_encoding))
            return False

        if hasattr(message, "deleted"):
            message.deleted = True

        return True

    def pin_message(self, message):
        """Pin a message

        Args:
            message (int | Message): Converting this to int must return a chat message ID.
        """

        assert self.session_cookie, "Not logged in, cannot pin message"
        return self.servicephp.chat_pin(self.stream_id_b10, message)

    def unpin_message(self, message = None):
        """Unpin the pinned message

        Args:
            message (None | int | Message): Message to unpin, defaults to known pinned message.
        """

        assert self.session_cookie, "Not logged in, cannot unpin message"
        if not message:
            message = self.pinned_message
        assert message, "No known pinned message and ID not provided"
        return self.servicephp.chat_pin(self.stream_id_b10, message, unpin = True)

    def mute_user(self, user, duration: int = None, total: bool = False):
        """Mute a user.

    Args:
        user (str): Username to mute.
        duration (int): How long to mute the user in seconds.
            Defaults to infinite.
        total (bool): Wether or not they are muted across all videos.
            Defaults to False, just this video.
            """

        assert self.session_cookie, "Not logged in, cannot mute user"
        return self.servicephp.mute_user(
            username = str(user),
            is_channel = False,
            video = self.stream_id_b10,
            duration = duration,
            total = total
            )

    def unmute_user(self, user):
        """Unmute a user.

    Args:
        user (str): Username to unmute
        """

        assert self.session_cookie, "Not logged in, cannot unmute user"

        # If the user object has a username attribute, use that
        #  because most user objects will __str__ into their base 36 ID
        if hasattr(user, "username"):
            user = user.username

        record_id = self.scraper.get_muted_user_record(str(user))
        assert record_id, "User was not in muted records"
        return self.servicephp.unmute_user(record_id)

    def __next_event_json(self):
        """Wait for the next event from the SSE and parse the JSON"""
        if not self.chat_running: # Do not try to query a new event if chat is closed
            print("Chat closed, cannot retrieve new JSON data.")
            return

        try:
            event = next(self.event_generator, None)
        except requests.exceptions.ReadTimeout:
            print("Request read timeout.")
            event = None

        if not event:
            self.chat_running = False # Chat has been closed
            print("Chat has closed.")
            return
        if not event.data: # Blank SSE event
            print("Blank SSE event:>", event, "<:")
            # Self recursion should work so long as we don't get dozens of blank events in a row
            return self.__next_event_json()

        return json.loads(event.data)

    def parse_init_data(self, jsondata):
        """Extract initial chat data from the SSE init event JSON

    Args:
        jsondata (dict): The JSON data returned by the initial SSE connection.
        """

        if jsondata["type"] != "init":
            print(jsondata)
            raise ValueError("That is not init json")

        # Parse pre-connection users, channels, then messages
        self.update_users(jsondata)
        self.update_channels(jsondata)
        self.update_mailbox(jsondata)

        # Load the chat badges
        self.load_badges(jsondata)

        self.rants_enabled = jsondata["data"]["config"]["rants"]["enable"]
        # subscription TODO
        # rant levels TODO
        self.message_length_max = jsondata["data"]["config"]["message_length_max"]

    def update_mailbox(self, jsondata):
        """Parse chat messages from an SSE data JSON

    Args:
        jsondata (dict): A JSON data block from an SSE event.
        """

        # Add new messages
        self.__mailbox += [Message(message_json, self) for message_json in jsondata["data"].get("messages", []) if int(message_json["id"]) not in self.__mailbox]

    def clear_mailbox(self):
        """Delete anything in the mailbox"""
        self.__mailbox = []

    def update_users(self, jsondata):
        """Update our dictionary of users from an SSE data JSON

    Args:
        jsondata (dict): A JSON data block from an SSE event.
        """

        for user_json in jsondata["data"].get("users", []):
            try:
                self.users[int(user_json["id"])]._jsondata = user_json # Update an existing user's JSON
            except KeyError: # User is new
                self.users[int(user_json["id"])] = User(user_json, self)

    def update_channels(self, jsondata):
        """Update our dictionary of channels from an SSE data JSON

    Args:
        jsondata (dict): A JSON data block from an SSE event.
        """

        for channel_json in jsondata["data"].get("channels", []):
            try:
                self.channels[int(channel_json["id"])]._jsondata = channel_json # Update an existing channel's JSON
            except KeyError: # Channel is new
                self.channels.update({int(channel_json["id"]) : Channel(channel_json, self)})

    def load_badges(self, jsondata):
        """Create our dictionary of badges from an SSE data JSON

    Args:
        jsondata (dict): A JSON data block from an SSE event.
        """

        self.badges = {badge_slug : UserBadge(badge_slug, jsondata["data"]["config"]["badges"][badge_slug], self) for badge_slug in jsondata["data"]["config"]["badges"].keys()}

    @property
    def stream_id_b10(self):
        """The chat ID in use"""
        return utils.base_36_to_10(self.stream_id)

    def get_message(self):
        """Return the next chat message (parsing any additional data).
        Waits for it to come in, returns None if chat closed.

    Returns:
        result (Message | None): Either the next chat message or NoneType.
        """

        # We don't already have messages
        while not self.__mailbox:
            jsondata = self.__next_event_json()

            # The chat has closed
            if not jsondata:
                return

            # Messages were deleted
            if jsondata["type"] in ("delete_messages", "delete_non_rant_messages"):
                # Flag the messages in our history as being deleted
                for message in self.__history:
                    if message.message_id in jsondata["data"]["message_ids"]:
                        message.deleted = True


            # Re-initialize (could contain new messages)
            elif jsondata["type"] == "init":
                self.parse_init_data(jsondata)

            # Pinned message
            elif jsondata["type"] == "pin_message":
                self.pinned_message = Message(jsondata["data"]["message"], self)

            # New messages
            elif jsondata["type"] == "messages":
                # Parse users, channels, then messages
                self.update_users(jsondata)
                self.update_channels(jsondata)
                self.update_mailbox(jsondata)

            # Unimplemented event type
            else:
                print("API sent an unimplemented SSE event type")
                print(jsondata)

        m = self.__mailbox.pop(0) # Get the oldest message in the mailbox
        self.__history.append(m) # Add the message to the history

        # Make sure the history is not too long, clipping off the oldest messages
        del self.__history[ : max((len(self.__history) - self.history_len, 0))]

        # Return the next message from the mailbox
        return m

history property

The chat history, trimmed to history_len

The session cookie we are logged in with

stream_id_b10 property

The chat ID in use

__init__(stream_id, username=None, password=None, session=None, history_len=1000)

The Rumble internal chat API

Parameters:

Name Type Description Default
stream_id (int, str)

Stream ID in base 10 int or base 36 str. WARNING: If a str is passed, this WILL ASSUME BASE 36 even if only digits are present! Convert to int before passing if it is base 10.

required
username str

Username to login with. Defaults to no login.

None
password str

Password to log in with. Defaults to no login.

None
session (str, dict)

Session token or cookie dict to authenticate with. Defaults to getting new session with username and password.

None
history_len int

Length of message history to store. Defaults to 1000.

1000
Source code in cocorum/chatapi.py
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
def __init__(self, stream_id, username: str = None, password: str = None, session = None, history_len = 1000):
    """The Rumble internal chat API

Args:
    stream_id (int, str): Stream ID in base 10 int or base 36 str.
        WARNING: If a str is passed, this WILL ASSUME BASE 36
        even if only digits are present! Convert to int before passing
        if it is base 10.
    username (str): Username to login with.
        Defaults to no login.
    password (str): Password to log in with.
        Defaults to no login.
    session (str, dict): Session token or cookie dict to authenticate with.
        Defaults to getting new session with username and password.
    history_len (int): Length of message history to store.
        Defaults to 1000.
        """

    self.stream_id = utils.ensure_b36(stream_id)

    self.__mailbox = []  #  A mailbox if you will
    self.__history = []  #  Chat history
    self.history_len = history_len  #  How many messages to store in history
    self.pinned_message = None  #  If a message is pinned, it is assigned to this
    self.users = {}  #  Dictionary of users by user ID
    self.channels = {}  #  Dictionary of channels by channel ID
    self.badges = {}

    # Generate our URLs
    self.sse_url = static.URI.ChatAPI.sse_stream.format(stream_id_b10 = self.stream_id_b10)
    print("SSE stream URL:", self.sse_url)
    self.message_api_url = static.URI.ChatAPI.message.format(stream_id_b10 = self.stream_id_b10)

    #  Connect to SSE stream
    #  Note: We do NOT want this request to have a timeout
    self.response = requests.get(self.sse_url, stream = True, headers = static.RequestHeaders.sse_api)
    self.client = sseclient.SSEClient(self.response)
    self.event_generator = self.client.events()
    self.chat_running = True

    #  If we have session login, use them
    if (username and password) or session:
        self.servicephp = ServicePHP(username, password, session)
        self.scraper = scraping.Scraper(self.servicephp)
    else:
        self.servicephp = None

    #  Parse the init data for the stream (must do AFTER we have servicephp)
    self.parse_init_data(self.__next_event_json())

    #  The last time we sent a message
    self.last_send_time = 0

__next_event_json()

Wait for the next event from the SSE and parse the JSON

Source code in cocorum/chatapi.py
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
def __next_event_json(self):
    """Wait for the next event from the SSE and parse the JSON"""
    if not self.chat_running: # Do not try to query a new event if chat is closed
        print("Chat closed, cannot retrieve new JSON data.")
        return

    try:
        event = next(self.event_generator, None)
    except requests.exceptions.ReadTimeout:
        print("Request read timeout.")
        event = None

    if not event:
        self.chat_running = False # Chat has been closed
        print("Chat has closed.")
        return
    if not event.data: # Blank SSE event
        print("Blank SSE event:>", event, "<:")
        # Self recursion should work so long as we don't get dozens of blank events in a row
        return self.__next_event_json()

    return json.loads(event.data)

clear_mailbox()

Delete anything in the mailbox

Source code in cocorum/chatapi.py
757
758
759
def clear_mailbox(self):
    """Delete anything in the mailbox"""
    self.__mailbox = []

close()

Close the chat connection

Source code in cocorum/chatapi.py
525
526
527
528
def close(self):
    """Close the chat connection"""
    self.response.close()
    self.chat_running = False

command(command_message)

Send a native chat command

Parameters:

Name Type Description Default
command_message str

The message you would send to launch this command in chat.

required

Returns:

Name Type Description
JSON dict

The JSON returned by the command.

Source code in cocorum/chatapi.py
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
def command(self, command_message: str):
    """Send a native chat command

Args:
    command_message (str): The message you would send to launch this command in chat.

Returns:
    JSON (dict): The JSON returned by the command.
    """

    assert command_message.startswith(static.Message.command_prefix), "Not a command message"
    r = requests.post(
        static.URI.ChatAPI.command,
        data = {
            "video_id" : self.stream_id_b10,
            "message" : command_message,
            },
        cookies = self.session_cookie,
        headers = static.RequestHeaders.user_agent,
        timeout = static.Delays.request_timeout,
        )
    assert r.status_code == 200, f"Command failed: {r}\n{r.text}"
    return r.json()

delete_message(message)

Delete a message in chat.

Parameters:

Name Type Description Default
message int | Message

Object which when converted to integer is the target message ID.

required

Returns:

Name Type Description
success bool

Wether the operation succeeded or not. NOTE: Method will also print an error message if it failed.

Source code in cocorum/chatapi.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
def delete_message(self, message):
    """Delete a message in chat.

Args:
    message (int | Message): Object which when converted to integer is the target message ID.

Returns:
    success (bool): Wether the operation succeeded or not.
        NOTE: Method will also print an error message if it failed.
    """

    assert not hasattr(message, "deleted") or not message.deleted, "Message was already deleted"

    assert self.session_cookie, "Not logged in, cannot delete message"
    assert utils.options_check(self.message_api_url + f"/{int(message)}", "DELETE"), "Rumble denied options request to delete message"

    r = requests.delete(
        self.message_api_url + f"/{int(message)}",
        cookies = self.session_cookie,
        #  headers = static.RequestHeaders.user_agent,
        timeout = static.Delays.request_timeout,
        )

    if r.status_code != 200:
        print("Error: Deleting message failed,", r, r.content.decode(static.Misc.text_encoding))
        return False

    if hasattr(message, "deleted"):
        message.deleted = True

    return True

get_message()

Return the next chat message (parsing any additional data). Waits for it to come in, returns None if chat closed.

Returns:

Name Type Description
result Message | None

Either the next chat message or NoneType.

Source code in cocorum/chatapi.py
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
def get_message(self):
    """Return the next chat message (parsing any additional data).
    Waits for it to come in, returns None if chat closed.

Returns:
    result (Message | None): Either the next chat message or NoneType.
    """

    # We don't already have messages
    while not self.__mailbox:
        jsondata = self.__next_event_json()

        # The chat has closed
        if not jsondata:
            return

        # Messages were deleted
        if jsondata["type"] in ("delete_messages", "delete_non_rant_messages"):
            # Flag the messages in our history as being deleted
            for message in self.__history:
                if message.message_id in jsondata["data"]["message_ids"]:
                    message.deleted = True


        # Re-initialize (could contain new messages)
        elif jsondata["type"] == "init":
            self.parse_init_data(jsondata)

        # Pinned message
        elif jsondata["type"] == "pin_message":
            self.pinned_message = Message(jsondata["data"]["message"], self)

        # New messages
        elif jsondata["type"] == "messages":
            # Parse users, channels, then messages
            self.update_users(jsondata)
            self.update_channels(jsondata)
            self.update_mailbox(jsondata)

        # Unimplemented event type
        else:
            print("API sent an unimplemented SSE event type")
            print(jsondata)

    m = self.__mailbox.pop(0) # Get the oldest message in the mailbox
    self.__history.append(m) # Add the message to the history

    # Make sure the history is not too long, clipping off the oldest messages
    del self.__history[ : max((len(self.__history) - self.history_len, 0))]

    # Return the next message from the mailbox
    return m

load_badges(jsondata)

Create our dictionary of badges from an SSE data JSON

Parameters:

Name Type Description Default
jsondata dict

A JSON data block from an SSE event.

required
Source code in cocorum/chatapi.py
787
788
789
790
791
792
793
794
def load_badges(self, jsondata):
    """Create our dictionary of badges from an SSE data JSON

Args:
    jsondata (dict): A JSON data block from an SSE event.
    """

    self.badges = {badge_slug : UserBadge(badge_slug, jsondata["data"]["config"]["badges"][badge_slug], self) for badge_slug in jsondata["data"]["config"]["badges"].keys()}

mute_user(user, duration=None, total=False)

Mute a user.

Parameters:

Name Type Description Default
user str

Username to mute.

required
duration int

How long to mute the user in seconds. Defaults to infinite.

None
total bool

Wether or not they are muted across all videos. Defaults to False, just this video.

False
Source code in cocorum/chatapi.py
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
def mute_user(self, user, duration: int = None, total: bool = False):
    """Mute a user.

Args:
    user (str): Username to mute.
    duration (int): How long to mute the user in seconds.
        Defaults to infinite.
    total (bool): Wether or not they are muted across all videos.
        Defaults to False, just this video.
        """

    assert self.session_cookie, "Not logged in, cannot mute user"
    return self.servicephp.mute_user(
        username = str(user),
        is_channel = False,
        video = self.stream_id_b10,
        duration = duration,
        total = total
        )

parse_init_data(jsondata)

Extract initial chat data from the SSE init event JSON

Parameters:

Name Type Description Default
jsondata dict

The JSON data returned by the initial SSE connection.

required
Source code in cocorum/chatapi.py
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
def parse_init_data(self, jsondata):
    """Extract initial chat data from the SSE init event JSON

Args:
    jsondata (dict): The JSON data returned by the initial SSE connection.
    """

    if jsondata["type"] != "init":
        print(jsondata)
        raise ValueError("That is not init json")

    # Parse pre-connection users, channels, then messages
    self.update_users(jsondata)
    self.update_channels(jsondata)
    self.update_mailbox(jsondata)

    # Load the chat badges
    self.load_badges(jsondata)

    self.rants_enabled = jsondata["data"]["config"]["rants"]["enable"]
    # subscription TODO
    # rant levels TODO
    self.message_length_max = jsondata["data"]["config"]["message_length_max"]

pin_message(message)

Pin a message

Parameters:

Name Type Description Default
message int | Message

Converting this to int must return a chat message ID.

required
Source code in cocorum/chatapi.py
639
640
641
642
643
644
645
646
647
def pin_message(self, message):
    """Pin a message

    Args:
        message (int | Message): Converting this to int must return a chat message ID.
    """

    assert self.session_cookie, "Not logged in, cannot pin message"
    return self.servicephp.chat_pin(self.stream_id_b10, message)

send_message(text, channel_id=None)

Send a message in chat.

Parameters:

Name Type Description Default
text str

The message text.

required
channel_id int

Numeric ID of the channel to use. Defaults to None.

None

Returns:

Name Type Description
ID int

The ID of the sent message.

User User

Your current chat user information.

Source code in cocorum/chatapi.py
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
def send_message(self, text: str, channel_id: int = None):
    """Send a message in chat.

Args:
    text (str): The message text.
    channel_id (int): Numeric ID of the channel to use.
        Defaults to None.

Returns:
    ID (int): The ID of the sent message.
    User (User): Your current chat user information.
    """

    assert self.session_cookie, "Not logged in, cannot send message"
    assert len(text) <= static.Message.max_len, "Mesage is too long"
    curtime = time.time()
    assert self.last_send_time + static.Message.send_cooldown <= curtime, "Sending messages too fast"
    assert utils.options_check(self.message_api_url, "POST"), "Rumble denied options request to post message"
    r = requests.post(
        self.message_api_url,
        cookies = self.session_cookie,
        json = {
            "data": {
                "request_id": utils.generate_request_id(),
                "message": {
                    "text": text
                },
                "rant": None,
                "channel_id": channel_id
                }
            },
        #  headers = static.RequestHeaders.user_agent,
        timeout = static.Delays.request_timeout,
        )

    if r.status_code != 200:
        print("Error: Sending message failed,", r, r.text)
        return

    return int(r.json()["data"]["id"]), User(r.json()["data"]["user"], self)

unmute_user(user)

Unmute a user.

Parameters:

Name Type Description Default
user str

Username to unmute

required
Source code in cocorum/chatapi.py
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
def unmute_user(self, user):
    """Unmute a user.

Args:
    user (str): Username to unmute
    """

    assert self.session_cookie, "Not logged in, cannot unmute user"

    # If the user object has a username attribute, use that
    #  because most user objects will __str__ into their base 36 ID
    if hasattr(user, "username"):
        user = user.username

    record_id = self.scraper.get_muted_user_record(str(user))
    assert record_id, "User was not in muted records"
    return self.servicephp.unmute_user(record_id)

unpin_message(message=None)

Unpin the pinned message

Parameters:

Name Type Description Default
message None | int | Message

Message to unpin, defaults to known pinned message.

None
Source code in cocorum/chatapi.py
649
650
651
652
653
654
655
656
657
658
659
660
def unpin_message(self, message = None):
    """Unpin the pinned message

    Args:
        message (None | int | Message): Message to unpin, defaults to known pinned message.
    """

    assert self.session_cookie, "Not logged in, cannot unpin message"
    if not message:
        message = self.pinned_message
    assert message, "No known pinned message and ID not provided"
    return self.servicephp.chat_pin(self.stream_id_b10, message, unpin = True)

update_channels(jsondata)

Update our dictionary of channels from an SSE data JSON

Parameters:

Name Type Description Default
jsondata dict

A JSON data block from an SSE event.

required
Source code in cocorum/chatapi.py
774
775
776
777
778
779
780
781
782
783
784
785
def update_channels(self, jsondata):
    """Update our dictionary of channels from an SSE data JSON

Args:
    jsondata (dict): A JSON data block from an SSE event.
    """

    for channel_json in jsondata["data"].get("channels", []):
        try:
            self.channels[int(channel_json["id"])]._jsondata = channel_json # Update an existing channel's JSON
        except KeyError: # Channel is new
            self.channels.update({int(channel_json["id"]) : Channel(channel_json, self)})

update_mailbox(jsondata)

Parse chat messages from an SSE data JSON

Parameters:

Name Type Description Default
jsondata dict

A JSON data block from an SSE event.

required
Source code in cocorum/chatapi.py
747
748
749
750
751
752
753
754
755
def update_mailbox(self, jsondata):
    """Parse chat messages from an SSE data JSON

Args:
    jsondata (dict): A JSON data block from an SSE event.
    """

    # Add new messages
    self.__mailbox += [Message(message_json, self) for message_json in jsondata["data"].get("messages", []) if int(message_json["id"]) not in self.__mailbox]

update_users(jsondata)

Update our dictionary of users from an SSE data JSON

Parameters:

Name Type Description Default
jsondata dict

A JSON data block from an SSE event.

required
Source code in cocorum/chatapi.py
761
762
763
764
765
766
767
768
769
770
771
772
def update_users(self, jsondata):
    """Update our dictionary of users from an SSE data JSON

Args:
    jsondata (dict): A JSON data block from an SSE event.
    """

    for user_json in jsondata["data"].get("users", []):
        try:
            self.users[int(user_json["id"])]._jsondata = user_json # Update an existing user's JSON
        except KeyError: # User is new
            self.users[int(user_json["id"])] = User(user_json, self)

ChatAPIObj

Bases: JSONObj

Object in the internal chat API

Source code in cocorum/chatapi.py
29
30
31
32
33
34
35
36
37
38
39
40
class ChatAPIObj(JSONObj):
    """Object in the internal chat API"""
    def __init__(self, jsondata, chat):
        """Object in the internal chat API

    Args:
        jsondata (dict): The JSON data block for the object.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """

        JSONObj.__init__(self, jsondata)
        self.chat = chat

__init__(jsondata, chat)

Object in the internal chat API

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the object.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
31
32
33
34
35
36
37
38
39
40
def __init__(self, jsondata, chat):
    """Object in the internal chat API

Args:
    jsondata (dict): The JSON data block for the object.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """

    JSONObj.__init__(self, jsondata)
    self.chat = chat

Chatter

Bases: JSONUserAction, ChatAPIObj

A user or channel in the internal chat API (abstract)

Source code in cocorum/chatapi.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class Chatter(JSONUserAction, ChatAPIObj):
    """A user or channel in the internal chat API (abstract)"""
    def __init__(self, jsondata, chat):
        """A user or channel in the internal chat API (abstract)

    Args:
        jsondata (dict): The JSON data block for the user/channel.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """
        ChatAPIObj.__init__(self, jsondata, chat)
        JSONUserAction.__init__(self, jsondata)

    @property
    def link(self):
        """The user's subpage of Rumble.com"""
        return self["link"]

The user's subpage of Rumble.com

__init__(jsondata, chat)

A user or channel in the internal chat API (abstract)

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the user/channel.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
44
45
46
47
48
49
50
51
52
def __init__(self, jsondata, chat):
    """A user or channel in the internal chat API (abstract)

Args:
    jsondata (dict): The JSON data block for the user/channel.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """
    ChatAPIObj.__init__(self, jsondata, chat)
    JSONUserAction.__init__(self, jsondata)

GiftPurchaseNotification

Bases: ChatAPIObj

A subscription gift under a message

Source code in cocorum/chatapi.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
class GiftPurchaseNotification(ChatAPIObj):
    """A subscription gift under a message"""
    def __init__(self, jsondata, message):
        """A subscription gift under a message

    Args:
        jsondata (dict): The JSON data block for the message.
        message (Message): The ChatAPI message object we are under
        """

        super().__init__(jsondata, message.chat)
        self.message = message

    @property
    def total_gifts(self) -> int:
        """The number of subscriptions in this gift"""
        return self["total_gifts"]

    @property
    def gift_type(self) -> str:
        """TODO"""
        return self["gift_type"]

    @property
    def video_id(self) -> int:
        """The numeric ID of the stream this gift was sent on, in base 10"""
        return self.chat.stream_id_b10

    @property
    def video_id_b10(self) -> int:
        """The numeric ID of the stream this gift was sent on, in base 10"""
        return self.video_id

    @property
    def video_id_b36(self) -> str:
        """The numeric ID of the stream this gift was sent on, in base 36"""
        return utils.base_10_to_36(self.video_id)

    @property
    def purchased_by(self) -> str:
        """The username of who purchased this gift"""
        return self.message.user.username

    @property
    def creator_user_id(self) -> int:
        """The numeric ID of the user whose stream this gift was given on, in base 10"""
        return self["creator_user_id"]

    @property
    def creator_user_id_b10(self) -> int:
        """The numeric ID of the user whose stream this gift was given on, in base 10"""
        return self.creator_user_id

    @property
    def creator_user_id_b36(self) -> str:
        """The numeric ID of the user whose stream this gift was given on, in base 36"""
        return utils.base_10_to_36(self.creator_user_id)

    @property
    def creator_channel_id(self) -> int:
        """The numeric ID of the channel whose stream this gift was given on, in base 10 (can be zero)"""
        return self["creator_channel_id"] if self["creator_channel_id"] else 0

    @property
    def creator_channel_id_b10(self) -> int:
        """The numeric ID of the channel whose stream this gift was given on, in base 10 (can be zero)"""
        return self.creator_channel_id

    @property
    def creator_channel_id_b36(self) -> str:
        """The numeric ID of the channel whose stream this gift was given on, in base 36 (can be zero)"""
        return utils.base_10_to_36(self.creator_channel_id)

creator_channel_id property

The numeric ID of the channel whose stream this gift was given on, in base 10 (can be zero)

creator_channel_id_b10 property

The numeric ID of the channel whose stream this gift was given on, in base 10 (can be zero)

creator_channel_id_b36 property

The numeric ID of the channel whose stream this gift was given on, in base 36 (can be zero)

creator_user_id property

The numeric ID of the user whose stream this gift was given on, in base 10

creator_user_id_b10 property

The numeric ID of the user whose stream this gift was given on, in base 10

creator_user_id_b36 property

The numeric ID of the user whose stream this gift was given on, in base 36

gift_type property

TODO

purchased_by property

The username of who purchased this gift

total_gifts property

The number of subscriptions in this gift

video_id property

The numeric ID of the stream this gift was sent on, in base 10

video_id_b10 property

The numeric ID of the stream this gift was sent on, in base 10

video_id_b36 property

The numeric ID of the stream this gift was sent on, in base 36

__init__(jsondata, message)

A subscription gift under a message

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the message.

required
message Message

The ChatAPI message object we are under

required
Source code in cocorum/chatapi.py
206
207
208
209
210
211
212
213
214
215
def __init__(self, jsondata, message):
    """A subscription gift under a message

Args:
    jsondata (dict): The JSON data block for the message.
    message (Message): The ChatAPI message object we are under
    """

    super().__init__(jsondata, message.chat)
    self.message = message

Message

Bases: ChatAPIObj

A single chat message in the internal chat API

Source code in cocorum/chatapi.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
class Message(ChatAPIObj):
    """A single chat message in the internal chat API"""
    def __init__(self, jsondata, chat):
        """A single chat message in the internal chat API

    Args:
        jsondata (dict): The JSON data block for the message.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """

        super().__init__(jsondata, chat)

        # Set the channel ID of our user if we can
        if self.user:
            self.user._set_channel_id = self.channel_id

        # Remember if we were deleted
        self.deleted = False

    def __eq__(self, other):
        """Compare this chat message with another

    Args:
        other (str, Message): Object to compare to.

    Returns:
        Comparison (bool, None): Did it fit the criteria?
        """

        if isinstance(other, str):
            return self.text == other

        # Check if the other object's text matches our own, if it has such
        if hasattr(other, "text"):
            # Check if the other object's user ID matches our own, if it has one
            if hasattr(other, "user_id"):
                # Check if the other object is a raid notification, if it says
                if hasattr(other, "raid_notification"):
                    return (self.user_id, self.text, self.raid_notification) == (other.user_id, other.text, other.raid_notification)

                return (self.user_id, self.text) == (other.user_id, other.text)

            # Check if the other object's username matches our own, if it has one
            if hasattr(other, "username"):
                # Check if the other object is a raid notification, if it says
                if hasattr(other, "raid_notification"):
                    return (self.user_id, self.text, self.raid_notification) == (other.user_id, other.text, other.raid_notification)

                return (self.user.username, self.text) == (other.username, other.text)

            # No user identifying attributes, but the text does match
            return self.text == other.text

    def __str__(self):
        """The chat message in string form"""
        return self.text

    def __int__(self):
        """The chat message in integer (ID) form"""
        return self.message_id

    @property
    def message_id(self):
        """The unique numerical ID of the chat message in base 10"""
        return int(self["id"])

    @property
    def message_id_b10(self):
        """The unique numerical ID of the chat message in base 10"""
        return self.message_id

    @property
    def message_id_b36(self):
        """The unique numerical ID of the chat message in base 36"""
        return utils.base_10_to_36(self.message_id)

    @property
    def time(self):
        """The time the message was sent on, in seconds since the Epoch UTC"""
        return utils.parse_timestamp(self["time"])

    @property
    def user_id(self):
        """The numerical ID of the user who posted the message in base 10"""
        return int(self["user_id"])

    @property
    def user_id_b10(self):
        """The numeric ID of the user in base 10"""
        return self.user_id

    @property
    def user_id_b36(self):
        """The numeric ID of the user in base 36"""
        return utils.base_10_to_36(self.user_id)

    @property
    def channel_id(self):
        """The numeric ID of the channel who posted the message, if there is one"""
        try:
            # Note: For some reason, channel IDs in messages alone show up as integers in the SSE events
            return int(self["channel_id"])
        except KeyError: # This user is not appearing as a channel and so has no channel ID
            return None

    @property
    def channel_id_b10(self):
        """The ID of the channel who posted the message in base 10"""
        return self.channel_id

    @property
    def channel_id_b36(self):
        """The ID of the channel who posted the message in base 36"""
        if not self.channel_id:
            return
        return utils.base_10_to_36(self.channel_id)

    @property
    def text(self):
        """The text of the message"""
        return self["text"]

    @property
    def user(self):
        """Reference to the user who posted this message"""
        try:
            return self.chat.users[self.user_id]
        except KeyError:
            print(f"ERROR: Message {self.message_id} could not reference user {self.user_id} because chat has no records of them as of yet.")

    @property
    def channel(self):
        """Reference to the channel that posted this message, if there was one"""
        if not self.channel_id:
            return None

        return self.chat.channels[self.channel_id]

    @property
    def is_rant(self):
        """Is this message a rant?"""
        return "rant" in self._jsondata

    @property
    def rant_price_cents(self):
        """The price of the rant, returns 0 if message is not a rant"""
        if not self.is_rant:
            return 0
        return self["rant"]["price_cents"]

    @property
    def rant_duration(self):
        """The duration the rant will show for, returns 0 if message is not a rant"""
        if not self.is_rant:
            return 0
        return self["rant"]["duration"]

    @property
    def rant_expires_on(self):
        """When the rant expires, returns message creation time if message is not a rant"""
        if not self.is_rant:
            return self.time
        return utils.parse_timestamp(self["rant"]["expires_on"])

    @property
    def raid_notification(self):
        """Are we a raid notification? Returns associated JSON data if yes, False if no"""
        return self.get("raid_notification", False)

    @property
    def gift_purchase_notification(self):
        """Are we a gifted subs notification? Returns associated JSON data if yes, False if no

    Returns:
        Data (GiftPurchaseNotification | bool): A simple container for the data, or False"""

        if self.get("gift_purchase_notification"):
            return GiftPurchaseNotification(self["gift_purchase_notification"], self)

        return False

    def delete(self):
        """Delete this message from the chat"""
        return self.chat.delete(self)

    def pin(self):
        """Pin this message"""
        return self.chat.pin_message(self)

    def unpin(self):
        """Unpin this message if it was pinned"""
        return self.chat.unpin_message(self)

channel property

Reference to the channel that posted this message, if there was one

channel_id property

The numeric ID of the channel who posted the message, if there is one

channel_id_b10 property

The ID of the channel who posted the message in base 10

channel_id_b36 property

The ID of the channel who posted the message in base 36

gift_purchase_notification property

Are we a gifted subs notification? Returns associated JSON data if yes, False if no

Returns:

Name Type Description
Data GiftPurchaseNotification | bool

A simple container for the data, or False

is_rant property

Is this message a rant?

message_id property

The unique numerical ID of the chat message in base 10

message_id_b10 property

The unique numerical ID of the chat message in base 10

message_id_b36 property

The unique numerical ID of the chat message in base 36

raid_notification property

Are we a raid notification? Returns associated JSON data if yes, False if no

rant_duration property

The duration the rant will show for, returns 0 if message is not a rant

rant_expires_on property

When the rant expires, returns message creation time if message is not a rant

rant_price_cents property

The price of the rant, returns 0 if message is not a rant

text property

The text of the message

time property

The time the message was sent on, in seconds since the Epoch UTC

user property

Reference to the user who posted this message

user_id property

The numerical ID of the user who posted the message in base 10

user_id_b10 property

The numeric ID of the user in base 10

user_id_b36 property

The numeric ID of the user in base 36

__eq__(other)

Compare this chat message with another

Parameters:

Name Type Description Default
other (str, Message)

Object to compare to.

required

Returns:

Name Type Description
Comparison (bool, None)

Did it fit the criteria?

Source code in cocorum/chatapi.py
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
def __eq__(self, other):
    """Compare this chat message with another

Args:
    other (str, Message): Object to compare to.

Returns:
    Comparison (bool, None): Did it fit the criteria?
    """

    if isinstance(other, str):
        return self.text == other

    # Check if the other object's text matches our own, if it has such
    if hasattr(other, "text"):
        # Check if the other object's user ID matches our own, if it has one
        if hasattr(other, "user_id"):
            # Check if the other object is a raid notification, if it says
            if hasattr(other, "raid_notification"):
                return (self.user_id, self.text, self.raid_notification) == (other.user_id, other.text, other.raid_notification)

            return (self.user_id, self.text) == (other.user_id, other.text)

        # Check if the other object's username matches our own, if it has one
        if hasattr(other, "username"):
            # Check if the other object is a raid notification, if it says
            if hasattr(other, "raid_notification"):
                return (self.user_id, self.text, self.raid_notification) == (other.user_id, other.text, other.raid_notification)

            return (self.user.username, self.text) == (other.username, other.text)

        # No user identifying attributes, but the text does match
        return self.text == other.text

__init__(jsondata, chat)

A single chat message in the internal chat API

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the message.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def __init__(self, jsondata, chat):
    """A single chat message in the internal chat API

Args:
    jsondata (dict): The JSON data block for the message.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """

    super().__init__(jsondata, chat)

    # Set the channel ID of our user if we can
    if self.user:
        self.user._set_channel_id = self.channel_id

    # Remember if we were deleted
    self.deleted = False

__int__()

The chat message in integer (ID) form

Source code in cocorum/chatapi.py
334
335
336
def __int__(self):
    """The chat message in integer (ID) form"""
    return self.message_id

__str__()

The chat message in string form

Source code in cocorum/chatapi.py
330
331
332
def __str__(self):
    """The chat message in string form"""
    return self.text

delete()

Delete this message from the chat

Source code in cocorum/chatapi.py
458
459
460
def delete(self):
    """Delete this message from the chat"""
    return self.chat.delete(self)

pin()

Pin this message

Source code in cocorum/chatapi.py
462
463
464
def pin(self):
    """Pin this message"""
    return self.chat.pin_message(self)

unpin()

Unpin this message if it was pinned

Source code in cocorum/chatapi.py
466
467
468
def unpin(self):
    """Unpin this message if it was pinned"""
    return self.chat.unpin_message(self)

User

Bases: Chatter, BaseUser

User in the internal chat API

Source code in cocorum/chatapi.py
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class User(Chatter, BaseUser):
    """User in the internal chat API"""
    def __init__(self, jsondata, chat):
        """A user in the internal chat API

    Args:
        jsondata (dict): The JSON data block for the user.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """

        Chatter.__init__(self, jsondata, chat)
        self.previous_channel_ids = [] # List of channels the user has appeared as, including the current one
        self._set_channel_id = None # Channel ID set from message
        self.servicephp = self.chat.servicephp

    @property
    def user_id(self):
        """The numeric ID of the user in base 10"""
        return int(self["id"])

    @property
    def channel_id(self):
        """The numeric channel ID that the user is appearing with in base 10"""

        # Try to get our channel ID from our own JSON (may be deprecated)
        try:
            new = int(self["channel_id"])

        # Rely on messages to have assigned our channel ID
        except KeyError:
            new = self._set_channel_id

        if new not in self.previous_channel_ids: # Record the appearance of a new chanel appearance, including None
            self.previous_channel_ids.append(new)
        return new

    @property
    def channel_id_b10(self):
        """The numeric channel ID that the user is appearing with in base 10"""
        return self.channel_id

    @property
    def channel_id_b36(self):
        """The numeric channel ID that the user is appearing with in base 36"""
        if not self.channel_id:
            return
        return utils.base_10_to_36(self.channel_id)

    @property
    def is_follower(self):
        """Is this user following the livestreaming channel?"""
        return self["is_follower"]

    @property
    def color(self):
        """The color of our username (RGB tuple)"""
        return tuple(int(self["color"][i : i + 2], 16) for i in range(0, 6, 2))

    @property
    def badges(self):
        """Badges the user has"""
        try:
            return [self.chat.badges[badge_slug] for badge_slug in self["badges"]]

        # User has no badges
        except KeyError:
            return []

badges property

Badges the user has

channel_id property

The numeric channel ID that the user is appearing with in base 10

channel_id_b10 property

The numeric channel ID that the user is appearing with in base 10

channel_id_b36 property

The numeric channel ID that the user is appearing with in base 36

color property

The color of our username (RGB tuple)

is_follower property

Is this user following the livestreaming channel?

user_id property

The numeric ID of the user in base 10

__init__(jsondata, chat)

A user in the internal chat API

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the user.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
61
62
63
64
65
66
67
68
69
70
71
72
def __init__(self, jsondata, chat):
    """A user in the internal chat API

Args:
    jsondata (dict): The JSON data block for the user.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """

    Chatter.__init__(self, jsondata, chat)
    self.previous_channel_ids = [] # List of channels the user has appeared as, including the current one
    self._set_channel_id = None # Channel ID set from message
    self.servicephp = self.chat.servicephp

UserBadge

Bases: ChatAPIObj, BaseUserBadge

A badge of a user

Source code in cocorum/chatapi.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
class UserBadge(ChatAPIObj, BaseUserBadge):
    """A badge of a user"""
    def __init__(self, slug, jsondata, chat):
        """A user badge in the internal chat API

    Args:
        jsondata (dict): The JSON data block for the user badge.
        chat (ChatAPI): The ChatAPI object that spawned us.
        """

        ChatAPIObj.__init__(self, jsondata, chat)
        self.slug = slug # The unique identification for this badge
        self.__icon = None

    @property
    def label(self):
        """A dictionary of lang:label pairs"""
        return self["label"]

    @property
    def icon_url(self):
        """The URL of the badge's icon"""
        return static.URI.rumble_base + self["icons"][static.Misc.badge_icon_size]

icon_url property

The URL of the badge's icon

label property

A dictionary of lang:label pairs

__init__(slug, jsondata, chat)

A user badge in the internal chat API

Parameters:

Name Type Description Default
jsondata dict

The JSON data block for the user badge.

required
chat ChatAPI

The ChatAPI object that spawned us.

required
Source code in cocorum/chatapi.py
182
183
184
185
186
187
188
189
190
191
192
def __init__(self, slug, jsondata, chat):
    """A user badge in the internal chat API

Args:
    jsondata (dict): The JSON data block for the user badge.
    chat (ChatAPI): The ChatAPI object that spawned us.
    """

    ChatAPIObj.__init__(self, jsondata, chat)
    self.slug = slug # The unique identification for this badge
    self.__icon = None

S.D.G.