summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/Talkbuchet-cli.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/docs/Talkbuchet-cli.py b/docs/Talkbuchet-cli.py
index 3b8ee18bd..9a39292a0 100644
--- a/docs/Talkbuchet-cli.py
+++ b/docs/Talkbuchet-cli.py
@@ -102,6 +102,17 @@ in the system running the browser when receiving video, although more when
sending it; Firefox sends a changing colour animation for video (640x480x30FPS,
~40 kBps), while Chrome sends an animation with the time since the video started
and a one second "clock" (640x480x20FPS, ~64 kBps).
+
+Besides the siege and virtual participant modes Talkbuchet-cli.py provides an
+additional mode, real participant, that is not part of Talkbuchet itself. This
+mode can be used to open browser instances and join the conversation and/or the
+call, exactly as it would be done by a real participant. However, please note
+that this mode is meant only for developing purposes; load/stress testing should
+be done with the other two modes, as the number of participants that can be
+simulated with them in the system running the test is much higher.
+
+Unlike the other modes, the real participant mode does not require an HPB server
+to be configured in Nextcloud Talk.
"""
import atexit
@@ -112,6 +123,8 @@ import websocket
from datetime import datetime
from pathlib import Path
from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.wait import WebDriverWait
from shutil import disk_usage
from time import sleep
@@ -817,6 +830,99 @@ class VirtualParticipant(TalkbuchetCommon):
self.seleniumHelper.execute('sendNickThroughDataChannel(\'' + nick + '\')')
+class RealParticipant():
+ """
+ Wrapper for Talkbuchet in real participant mode.
+
+ This wrapper exposes functions to use a real participant in a browser.
+ """
+
+ def __init__(self, browser, nextcloudUrl, headless = True, remoteSeleniumUrl = None):
+ """
+ Starts a real participant in the given Nextcloud URL using the given
+ browser.
+
+ :param browser: "firefox" or "chrome".
+ :param nextcloudUrl: the URL of the Nextcloud instance to start the real
+ participant in.
+ :param headless: whether the browser will be started in headless mode or
+ not; headless mode is used by default.
+ :param remoteSeleniumUrl: the URL of the Selenium server to connect to;
+ the local server is used by default.
+ """
+
+ self.nextcloudUrl = nextcloudUrl
+
+ self.seleniumHelper = SeleniumHelper()
+
+ if browser == 'chrome':
+ self.seleniumHelper.startChrome(headless, remoteSeleniumUrl)
+ elif browser == 'firefox':
+ self.seleniumHelper.startFirefox(headless, remoteSeleniumUrl)
+ else:
+ raise Exception('Invalid browser: ' + browser)
+
+ self.seleniumHelper.driver.get(nextcloudUrl)
+
+ def login(self, user, appToken):
+ """
+ Logs in Nextcloud as the given user with the given app token.
+
+ :param user: the ID of the user to log as.
+ :param appToken: an app token of the user.
+ """
+
+ # Fetching a Nextcloud URL in the browser console with a user and an app
+ # token implicitly does a login with that user. Visiting any page in the
+ # Nextcloud server will be done as a logged in user after that.
+ self.seleniumHelper.executeAsync('''
+ const fetchOptions = {
+ headers: {
+ 'Authorization': 'Basic ' + btoa(\'''' + user + ':' + appToken + '''\'),
+ },
+ }
+
+ await fetch(\'''' + self.nextcloudUrl + '''\', fetchOptions)
+ ''')
+
+ def joinRoom(self, token):
+ """
+ Joins the room with the given token.
+
+ If no login was done before the participant will join as a guest.
+
+ :param token: the token of the room to join.
+ """
+
+ self.seleniumHelper.driver.get(self.nextcloudUrl + '/call/' + token)
+
+ def joinCall(self):
+ """
+ Joins (or starts) the call in the current room.
+
+ A room must have been joined before joining the call.
+ """
+
+ self.seleniumHelper.driver.find_element(By.CSS_SELECTOR, '.top-bar #call_button').click()
+
+ try:
+ # If the device selector is shown click on the "Join call" button
+ # in the dialog to actually join the call.
+ WebDriverWait(self.seleniumHelper.driver, timeout=5).until(lambda driver: driver.find_element(By.CSS_SELECTOR, '.device-checker #call_button'))
+ self.seleniumHelper.driver.find_element(By.CSS_SELECTOR, '.device-checker #call_button').click()
+ except:
+ pass
+
+ def leaveCall(self):
+ """
+ Leaves the current call.
+
+ The call must have been joined first.
+ """
+
+ self.seleniumHelper.driver.find_element(By.CSS_SELECTOR, '.top-bar #call_button').click()
+
+
_talkbuchetMode = ''
_browser = ''
@@ -1195,6 +1301,15 @@ def switchToSiegeMode():
del globals()['removeVirtualParticipant']
del globals()['removeVirtualParticipants']
+ if globals()['_talkbuchetMode'] == 'realParticipant':
+ if removeRealParticipants:
+ removeRealParticipants()
+
+ del globals()['addRealParticipant']
+ del globals()['addRealParticipants']
+ del globals()['removeRealParticipant']
+ del globals()['removeRealParticipants']
+
globals()['setPublishersAndSubscribersCount'] = setPublishersAndSubscribersCount
globals()['startSiege'] = startSiege
globals()['checkPublishersConnections'] = checkPublishersConnections
@@ -1347,6 +1462,15 @@ def switchToVirtualParticipantMode():
del globals()['checkSubscribersConnections']
del globals()['endSiege']
+ if globals()['_talkbuchetMode'] == 'realParticipant':
+ if removeRealParticipants:
+ removeRealParticipants()
+
+ del globals()['addRealParticipant']
+ del globals()['addRealParticipants']
+ del globals()['removeRealParticipant']
+ del globals()['removeRealParticipants']
+
globals()['addVirtualParticipant'] = addVirtualParticipant
globals()['addVirtualParticipants'] = addVirtualParticipants
globals()['removeVirtualParticipant'] = removeVirtualParticipant
@@ -1355,6 +1479,147 @@ def switchToVirtualParticipantMode():
globals()['_talkbuchetMode'] = 'virtualParticipant'
+realParticipants = []
+
+def switchToRealParticipantMode():
+ """
+ Sets the real participant mode as the active one.
+
+ This adjusts the global helper functions to those relevant for this mode
+ (so, for example, there will be no function to start a siege).
+
+ Real participants can be added to a conversation in the following way:
+ >>>> setTarget('https://THE-NEXTCLOUD-DOMAIN')
+ >>>> setToken('THE-CONVERSATION-TOKEN')
+ >>>> addRealParticipants(NUMBER-OF-REAL-PARTICIPANTS)
+
+ If no credentials are set the added participants will be guests. To add a
+ registered user (note that the same user can be added several times) set the
+ credentials first before adding the real participants:
+ >>>> setCredentials('THE-USER-ID', 'THE-APP-TOKEN')
+
+ Note that adding a new participant does not remove the previous ones. That
+ should be explicitly done by calling "removeRealParticipants()" (or
+ "removeRealParticipant(INDEX)" to remove just a specific participant).
+ Therefore, it is possible to add several participants with different
+ parameters (like several guests and then several users) by setting the
+ parameters, calling "addRealParticipants(NUMBER)", setting the new
+ parameters and calling "addRealParticipants(NUMBER)" again.
+
+ Global functions provided for real participants only cover joining and
+ leaving the conversation. Joining the call must be directly done on the
+ Talkbuchet wrapper objects in the "realParticipants" list with:
+ >>>> realParticipants[INDEX].joinCall()
+ """
+
+ def _isValidConfiguration():
+ if not _isValidBrowser():
+ return False
+
+ if not _nextcloudUrl:
+ print("Set target Nextcloud URL first")
+ return False
+
+ if not _token:
+ print("Set conversation token first")
+ return False
+
+ return True
+
+ def addRealParticipant():
+ """
+ Adds a single real participant.
+
+ The global target Nextcloud URL and conversation token need to be set
+ first.
+
+ If global credentials were set the user will be logged in with them.
+
+ Note that changing any of those values later will have no effect on an
+ existing real participant, the updated value will be used only by real
+ participants added after they were changed.
+ """
+
+ if not _isValidConfiguration():
+ return
+
+ realParticipant = RealParticipant(_getBrowser(), _nextcloudUrl, _headless, _remoteSeleniumUrl)
+
+ realParticipants.append(realParticipant)
+
+ if _user or _appToken:
+ realParticipant.login(_user, _appToken)
+
+ realParticipant.joinRoom(_token)
+
+ def addRealParticipants(count):
+ """
+ Adds as many real participants as the given count.
+
+ See :py:func:`addRealParticipant`.
+
+ :param count: the number of real participants to add.
+ """
+
+ if not _isValidConfiguration():
+ return
+
+ for i in range(count):
+ addRealParticipant()
+
+ print('.', end='', flush=True)
+
+ print("")
+
+ def removeRealParticipant(index):
+ """
+ Removes the real participant with the given index.
+
+ :param index: the index in :py:data:`realParticipants` of the real
+ participant to remove.
+ """
+
+ if index < 0 or index >= len(realParticipants):
+ print("Index out of range")
+ return
+
+ del realParticipants[index]
+
+ def removeRealParticipants():
+ """
+ Removes all the real participants previously added.
+ """
+
+ while realParticipants:
+ removeRealParticipant(0)
+
+ if globals()['_talkbuchetMode'] == 'siege':
+ if endSiege:
+ endSiege()
+
+ del globals()['setPublishersAndSubscribersCount']
+ del globals()['startSiege']
+ del globals()['checkPublishersConnections']
+ del globals()['checkSubscribersConnections']
+ del globals()['endSiege']
+
+ if globals()['_talkbuchetMode'] == 'virtualParticipant':
+ if removeVirtualParticipants:
+ removeVirtualParticipants()
+
+ del globals()['addVirtualParticipant']
+ del globals()['addVirtualParticipants']
+ del globals()['removeVirtualParticipant']
+ del globals()['removeVirtualParticipants']
+
+ globals()['addRealParticipant'] = addRealParticipant
+ globals()['addRealParticipants'] = addRealParticipants
+ globals()['removeRealParticipant'] = removeRealParticipant
+ globals()['removeRealParticipants'] = removeRealParticipants
+
+ globals()['_talkbuchetMode'] = 'realParticipant'
+
+
def _deleteTalkbuchetInstancesOnExit():
while sieges:
del sieges[0]
@@ -1362,6 +1627,9 @@ def _deleteTalkbuchetInstancesOnExit():
while virtualParticipants:
del virtualParticipants[0]
+ while realParticipants:
+ del realParticipants[0]
+
# Talkbuchet instances should be explicitly deleted before exiting, as if they
# are implicitly deleted while exiting the Selenium driver may not cleanly quit.
atexit.register(_deleteTalkbuchetInstancesOnExit)