Skip to content

Commit 08dd046

Browse files
committed
Completed #1, shortened multiple lines
1 parent a2b84e7 commit 08dd046

File tree

1 file changed

+107
-37
lines changed

1 file changed

+107
-37
lines changed

src/rumchat_actor/__init__.py

Lines changed: 107 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
from selenium.webdriver.common.alert import Alert
1616
from selenium.webdriver.common.by import By
1717
from selenium.webdriver.common.keys import Keys
18+
from selenium.webdriver.support import expected_conditions as EC
19+
from selenium.webdriver.support.ui import WebDriverWait
1820

19-
#How long to wait after performing any browser action, for the webpage to load its response
20-
BROWSER_ACTION_DELAY = 2
21+
#How long to wait maximum for a condition to be true in the browser
22+
BROWSER_WAIT_TIMEOUT = 30
2123

2224
#How long to wait between sending messages
2325
SEND_MESSAGE_COOLDOWN = 3
@@ -52,18 +54,21 @@
5254

5355
class ChatCommand():
5456
"""A chat command, internal use only"""
55-
def __init__(self, name, actor, cooldown = BROWSER_ACTION_DELAY, amount_cents = 0, exclusive = False, allowed_badges = ["subscriber"], whitelist_badges = ["moderator"], target = None):
57+
def __init__(self, name, actor, cooldown = SEND_MESSAGE_COOLDOWN, amount_cents = 0, exclusive = False, allowed_badges = ["subscriber"], whitelist_badges = ["moderator"], target = None):
5658
"""name: The !name of the command
5759
actor: The RumleChatActor host object
5860
amount_cents: The minimum cost of the command. Defaults to free
5961
exclusive: If this command can only be run by users with allowed badges. Defaults to False
60-
allowed_badges: Badges that are allowed to run this command (if it is exclusive). Defaults to subscribers, admin is added internally.
62+
allowed_badges: Badges that are allowed to run this command (if it is exclusive).
63+
Defaults to subscribers, admin is added internally.
6164
whitelist_badges: Badges which if borne give the user free-of-charge command access
62-
target: The function(message, actor) to call on successful command usage. Defaults to self.run"""
65+
target: The command function(message, actor) to call. Defaults to self.run"""
6366
assert " " not in name, "Name cannot contain spaces"
6467
self.name = name
6568
self.actor = actor
66-
assert cooldown >= BROWSER_ACTION_DELAY, f"Cannot set a cooldown shorter than {BROWSER_ACTION_DELAY}"
69+
assert cooldown >= SEND_MESSAGE_COOLDOWN, \
70+
f"Cannot set a cooldown shorter than {SEND_MESSAGE_COOLDOWN}"
71+
6772
self.cooldown = cooldown
6873
self.amount_cents = amount_cents #Cost of the command
6974
self.exclusive = exclusive
@@ -75,19 +80,31 @@ def __init__(self, name, actor, cooldown = BROWSER_ACTION_DELAY, amount_cents =
7580
def call(self, message):
7681
"""The command was called"""
7782
#this command is exclusive, and the user does not have the required badge
78-
if self.exclusive and not (True in [badge.slug in self.allowed_badges for badge in message.user.badges]):
79-
self.actor.send_message(f"@{message.user.username} That command is exclusive to: " + ", ".join(self.allowed_badges))
83+
if self.exclusive and \
84+
not (True in [badge.slug in self.allowed_badges for badge in message.user.badges]):
85+
86+
self.actor.send_message(f"@{message.user.username} That command is exclusive to: " +
87+
", ".join(self.allowed_badges)
88+
)
89+
8090
return
8191

8292
#The command is still on cooldown
8393
if (curtime := time.time()) - self.last_use_time < self.cooldown:
84-
self.actor.send_message(f"@{message.user.username} That command is still on cooldown. Try again in {int(self.last_use_time + self.cooldown - curtime + 0.5)} seconds.")
94+
self.actor.send_message(
95+
f"@{message.user.username} That command is still on cooldown. " +
96+
f"Try again in {int(self.last_use_time + self.cooldown - curtime + 0.5)} seconds."
97+
)
8598

8699
return
87100

88101
#the user did not pay enough for the command and they do not have a free pass
89-
if message.rant_price_cents < self.amount_cents and not (True in [badge.slug in self.whitelist_badges for badge in message.user.badges]):
90-
self.actor.send_message(f"@{message.user.username} That command costs ${self.amount_cents/100:.2f}.")
102+
if message.rant_price_cents < self.amount_cents and \
103+
not (True in [badge.slug in self.whitelist_badges for badge in message.user.badges]):
104+
105+
self.actor.send_message("@" + message.user.username +
106+
f"That command costs ${self.amount_cents/100:.2f}."
107+
)
91108
return
92109

93110
#the command was called successfully
@@ -103,7 +120,9 @@ def run(self, message):
103120
return
104121

105122
#Run method was never defined
106-
self.actor.send_message(f"@{message.user.username} Hello, this command never had a target defined. :-)")
123+
self.actor.send_message("@" + message.user.username +
124+
"Hello, this command never had a target defined. :-)"
125+
)
107126

108127
class RumbleChatActor():
109128
"""Actor that interacts with Rumble chat"""
@@ -116,7 +135,7 @@ def __init__(self, stream_id = None, init_message = "Hello, Rumble world!", prof
116135
streamer_username: The username of the person streaming
117136
streamer_channel: The channel doing the livestream
118137
is_channel_stream: If the livestream is on a channel or not
119-
ignore_users: List of usernames, will ignore all their messages, useful if you are using TheRumbleBot"""
138+
ignore_users: List of usernames, will ignore all their messages"""
120139

121140
#The info of the person streaming
122141
self.__streamer_username = streamer_username
@@ -133,7 +152,8 @@ def __init__(self, stream_id = None, init_message = "Hello, Rumble world!", prof
133152
if stream_id:
134153
self.stream_id, self.stream_id_b10 = utils.stream_id_36_and_10(stream_id)
135154

136-
#It is not our livestream or we have no Live Stream API, LS API functions are not available
155+
#It is not our livestream or we have no Live Stream API,
156+
#so LS API functions are not available
137157
if not self.rum_api or self.stream_id not in self.rum_api.livestreams:
138158
self.api_stream = None
139159

@@ -173,17 +193,29 @@ def __init__(self, stream_id = None, init_message = "Hello, Rumble world!", prof
173193
#We have credentials
174194
if username and password:
175195
sign_in_buttn.click()
176-
time.sleep(BROWSER_ACTION_DELAY)
177-
self.browser.find_element(By.ID, "login-username").send_keys(username + Keys.RETURN)
196+
WebDriverWait(self.browser, BROWSER_WAIT_TIMEOUT).until(
197+
EC.visibility_of_element_located(By.ID, "login-username"),
198+
"Timed out waiting for sign-in dialouge"
199+
)
200+
201+
uname_field = self.browser.find_element(By.ID, "login-username")
202+
uname_field.send_keys(username + Keys.RETURN)
178203
self.browser.find_element(By.ID, "login-password").send_keys(password + Keys.RETURN)
179-
break #We only need to do that once
180204

181205
#We do not have credentials, ask for manual sign in
182206
self.browser.maximize_window()
183207
input("Please log in at the browser, then press enter here.")
184208

185-
#Wait for signed in loading to complete
186-
time.sleep(BROWSER_ACTION_DELAY)
209+
#Wait for signed in loading to complete
210+
WebDriverWait(self.browser, BROWSER_WAIT_TIMEOUT).until(
211+
EC.invisibility_of_element(uname_field),
212+
"Timeout waiting for username field to disappear"
213+
)
214+
215+
WebDriverWait(self.browser, BROWSER_WAIT_TIMEOUT).until(
216+
EC.visibility_of_element_located(By.ID, "chat-message-text-input"),
217+
"Timed out waiting for chat message field to appear"
218+
)
187219

188220
#Find our username
189221
if username:
@@ -198,9 +230,6 @@ def __init__(self, stream_id = None, init_message = "Hello, Rumble world!", prof
198230
#Ignore these users when processing messages
199231
self.ignore_users = ignore_users
200232

201-
#Wait for potential further page load?
202-
time.sleep(BROWSER_ACTION_DELAY)
203-
204233
#History of the bot's messages so they do not get loop processed
205234
self.sent_messages = []
206235

@@ -224,9 +253,11 @@ def __init__(self, stream_id = None, init_message = "Hello, Rumble world!", prof
224253
while (m := self.ssechat.get_message()).user.username != self.username:
225254
pass
226255

227-
assert "moderator" in m.user.badges or "admin" in m.user.badges, "Actor cannot function without being a moderator"
256+
assert "moderator" in m.user.badges or "admin" in m.user.badges, \
257+
"Actor cannot function without being a moderator"
228258

229-
#Functions that are to be called on each message, must return False if the message was deleted
259+
#Functions that are to be called on each message,
260+
#must return False if the message was deleted
230261
self.message_actions = []
231262

232263
#Instances of RumbleChatCommand, by name
@@ -252,7 +283,10 @@ def streamer_channel(self):
252283
#We are the ones streaming, and the API URL is under the channel
253284
if self.api_stream and self.rum_api.channel_name:
254285
self.__streamer_channel = self.rum_api.channel_name
255-
#We are not the ones streaming, or the API URL was not under our channel, and we can confirm this is a channel stream
286+
287+
#We are not the ones streaming,
288+
#or the API URL was not under our channel,
289+
#and we are sure this is a channel stream
256290
else:
257291
self.__streamer_channel = input("Enter the channel of the person streaming: ")
258292

@@ -307,7 +341,9 @@ def _sender_loop(self):
307341

308342
def __send_message(self, text):
309343
"""Send a message in chat"""
310-
assert len(text) < MAX_MESSAGE_LEN, f"Message with prefix cannot be longer than {MAX_MESSAGE_LEN} characters"
344+
assert len(text) < MAX_MESSAGE_LEN, \
345+
f"Message with prefix cannot be longer than {MAX_MESSAGE_LEN} characters"
346+
311347
self.sent_messages.append(text)
312348
self.last_message_send_time = time.time()
313349
self.browser.find_element(By.ID, "chat-message-text-input").send_keys(text + Keys.RETURN)
@@ -327,12 +363,20 @@ def open_moderation_menu(self, message):
327363
#Find the message by ID
328364
elif isinstance(message, int):
329365
message_id = message
330-
message_li = self.browser.find_element(By.XPATH, f"//li[@class='chat-history--row js-chat-history-item'][@data-message-id='{message_id}']")
366+
message_li = self.browser.find_element(
367+
By.XPATH,
368+
"//li[@class='chat-history--row js-chat-history-item']" +
369+
f"[@data-message-id='{message_id}']"
370+
)
331371

332372
#The message has a message ID attribute
333373
elif hasattr(message, "message_id"):
334374
message_id = message.message_id
335-
message_li = self.browser.find_element(By.XPATH, f"//li[@class='chat-history--row js-chat-history-item'][@data-message-id='{message_id}']")
375+
message_li = self.browser.find_element(
376+
By.XPATH,
377+
"//li[@class='chat-history--row js-chat-history-item']" +
378+
f"[@data-message-id='{message_id}']"
379+
)
336380

337381
#Not a valid message type
338382
else:
@@ -341,7 +385,10 @@ def open_moderation_menu(self, message):
341385
#Hover over the message
342386
self.hover_element(message_li)
343387
#Find the moderation menu
344-
menu_bttn = self.browser.find_element(By.XPATH, f"//li[@class='chat-history--row js-chat-history-item'][@data-message-id='{message_id}']/button[@class='js-moderate-btn chat-history--kebab-button']")
388+
menu_bttn = message_li.find_element(
389+
By.XPATH,
390+
".//button[@class='js-moderate-btn chat-history--kebab-button']"
391+
)
345392
#Click the moderation menu button
346393
menu_bttn.click()
347394

@@ -350,23 +397,40 @@ def open_moderation_menu(self, message):
350397
def delete_message(self, message):
351398
"""Delete a message in the chat"""
352399
m_id = self.open_moderation_menu(message)
353-
del_bttn = self.browser.find_element(By.XPATH, f"//button[@class='cmi js-btn-delete-current'][@data-message-id='{m_id}']")
400+
del_bttn = self.browser.find_element(
401+
By.XPATH,
402+
f"//button[@class='cmi js-btn-delete-current'][@data-message-id='{m_id}']"
403+
)
404+
354405
del_bttn.click()
355-
time.sleep(BROWSER_ACTION_DELAY)
406+
407+
#Wait for the confirmation to appear
408+
WebDriverWait(self.browser, BROWSER_WAIT_TIMEOUT).until(
409+
EC.alert_is_present(),
410+
"Timed out waiting for deletion confirmation dialouge to appear"
411+
)
356412

357413
#Confirm the confirmation dialog
358414
Alert(self.browser).accept()
359415

360416
def mute_by_message(self, message, mute_level = "5"):
361417
"""Mute a user by message"""
362418
self.open_moderation_menu(message)
363-
timeout_bttn = self.browser.find_element(By.XPATH, f"//button[@class='{MUTE_LEVELS[mute_level]}']")
419+
timeout_bttn = self.browser.find_element(
420+
By.XPATH,
421+
f"//button[@class='{MUTE_LEVELS[mute_level]}']"
422+
)
423+
364424
timeout_bttn.click()
365425

366426
def mute_by_appearname(self, name, mute_level = "5"):
367427
"""Mute a user by the name they are appearing with"""
368428
#Find any chat message by this user
369-
message_li = self.browser.find_element(By.XPATH, f"//li[@class='chat-history--row js-chat-history-item'][@data-username='{name}']")
429+
message_li = self.browser.find_element(
430+
By.XPATH,
431+
f"//li[@class='chat-history--row js-chat-history-item'][@data-username='{name}']"
432+
)
433+
370434
self.mute_by_message(message = message_li, mute_level = mute_level)
371435

372436
def pin_message(self, message):
@@ -378,7 +442,11 @@ def pin_message(self, message):
378442
def unpin_message(self):
379443
"""Unpin the currently pinned message"""
380444
try:
381-
unpin_bttn = self.browser.find_element(By.XPATH, "//button[@data-js='remove_pinned_message_button']")
445+
unpin_bttn = self.browser.find_element(
446+
By.XPATH,
447+
"//button[@data-js='remove_pinned_message_button']"
448+
)
449+
382450
except selenium.common.exceptions.NoSuchElementException:
383451
return False #No message was pinned
384452

@@ -412,7 +480,8 @@ def register_command(self, command, name = None):
412480
"""Register a command"""
413481
#Is a ChatCommand instance
414482
if isinstance(command, ChatCommand):
415-
assert not name or name == command.name, "ChatCommand instance has different name than one passed"
483+
assert not name or name == command.name, \
484+
"ChatCommand instance has different name than one passed"
416485
self.chat_commands[command.name] = command
417486

418487
#Is a callable
@@ -435,7 +504,7 @@ def __process_message(self, message):
435504
if message.text in self.sent_messages:
436505
return
437506

438-
#If the message is from the same account as us, reset our message cooldown from the message send time if it is newer
507+
#If the message is from the same account as us, consider it in message send cooldown
439508
if message.user.username == self.username:
440509
self.last_message_send_time = max((self.last_message_send_time, message.time))
441510

@@ -444,7 +513,8 @@ def __process_message(self, message):
444513
return
445514

446515
for action in self.message_actions:
447-
if message.message_id in self.ssechat.deleted_message_ids or not action(message, self): #The message got deleted, possibly by this action
516+
#The message got deleted, possibly by this action
517+
if message.message_id in self.ssechat.deleted_message_ids or not action(message, self):
448518
return
449519

450520
self.__run_if_command(message)

0 commit comments

Comments
 (0)