• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Utility class for sending requests to BonD, adding and controlling bots."""
2
3from __future__ import absolute_import
4from __future__ import division
5from __future__ import print_function
6
7import json
8import logging
9import requests
10
11from datetime import datetime
12from oauth2client.client import GoogleCredentials
13
14
15_BOND_API_URL = 'https://bond-pa.sandbox.googleapis.com'
16_HANGOUTS_API_URL = 'https://preprod-hangouts.googleapis.com/hangouts/v1_meetings/'
17_MEETINGS_API_URL = 'https://preprod-meetings.sandbox.googleapis.com'
18
19_TOKEN_TTL_SECONDS = 3500
20
21# See https://crbug.com/874835 for details on the credentials files.
22_SERVICE_CREDS_FILE = '/creds/service_accounts/bond_service_account.json'
23
24
25class BondHttpApi(object):
26  """Utility class for sending requests to BonD for bots."""
27
28  def __init__(self):
29    self._last_token_request_time = None
30    self._last_token = None
31
32  def GetAvailableWorkers(self):
33    """Gets the number of available workers for a conference."""
34    token = self._GetAccessToken()
35    resp = requests.get(
36        '%s/v1/workers:count' % _BOND_API_URL,
37        headers={
38            'Content-Type': 'application/json',
39            'Authorization': 'Bearer %s' % token
40        })
41    return json.loads(resp.text)["numOfAvailableWorkers"]
42
43  def CreateConference(self):
44    """Creates a conference.
45
46    Returns:
47      The meeting code of the created conference.
48    """
49    token = self._GetAccessToken()
50
51    request_data = {
52      'conference_type': 'THOR',
53      'backend_options': {
54        'mesi_apiary_url': _HANGOUTS_API_URL,
55        'mas_one_platform_url': _MEETINGS_API_URL
56      },
57    }
58
59    resp = requests.post(
60        '%s/v1/conferences:create' % _BOND_API_URL,
61        headers={
62            'Content-Type': 'application/json',
63            'Authorization': 'Bearer %s' % token,
64        },
65        data=json.dumps(request_data))
66    json_response = json.loads(resp.text)
67    logging.info("CreateConference response: %s", json_response)
68    return json_response["conference"]["conferenceCode"]
69
70  def ExecuteScript(self, script, meeting_code):
71    """Executes the specified script.
72
73    Args:
74      script: Script to execute.
75      meeting_code: The meeting to execute the script for.
76
77    Returns:
78      RunScriptRequest denoting failure or success of the request.
79    """
80    token = self._GetAccessToken()
81
82    request_data = {
83      'script': script,
84      'conference': {
85        'conference_code': meeting_code
86      }
87     }
88
89    resp = requests.post(
90        '%s/v1/conference/%s/script' % (_BOND_API_URL, meeting_code),
91        headers={
92            'Content-Type': 'application/json',
93            'Authorization': 'Bearer %s' % token,
94        },
95        data=json.dumps(request_data))
96
97    json_response = json.loads(resp.text)
98    logging.info("ExecuteScript response: %s", json_response)
99    return json_response['success']
100
101
102  def AddBotsRequest(self,
103                     meeting_code,
104                     number_of_bots,
105                     ttl_secs,
106                     send_fps=24,
107                     requested_layout='BRADY_BUNCH_4_4',
108                     allow_vp9=True,
109                     send_vp9=True):
110    """Adds a number of bots to a meeting for a specified period of time.
111
112    Args:
113      meeting_code: The meeting to join.
114      number_of_bots: The number of bots to add to the meeting.
115      ttl_secs: The time in seconds that the bots will stay in the meeting after
116        joining.
117      send_fps: FPS the bots are sending.
118      requested_layout: The type of layout the bots are requesting. Valid values
119        include CLASSIC, BRADY_BUNCH_4_4 and BRADY_BUNCH_7_7.
120      allow_vp9: If set, VP9 will be negotiated for devices where support is
121        available. This only activates receiving VP9. See `send_vp9` for sending
122      send_vp9: If set, VP9 will be preferred over VP8 when sending.
123        `allow_vp9` must also be set for VP9 to be negotiated.
124
125    Returns:
126      List of IDs of the started bots.
127    """
128    # Not allowing VP9, but sending it is an invalid combination.
129    assert(allow_vp9 or not send_vp9)
130    token = self._GetAccessToken()
131
132    request_data = {
133      'num_of_bots': number_of_bots,
134      'ttl_secs': ttl_secs,
135      'video_call_options': {
136        'allow_vp9': allow_vp9,
137        'send_vp9': send_vp9,
138      },
139      'media_options': {
140        'audio_file_path': "audio_32bit_48k_stereo.raw",
141        'mute_audio': True,
142        'video_fps': send_fps,
143        'mute_video': False,
144        'requested_layout': requested_layout,
145      },
146      'backend_options': {
147        'mesi_apiary_url': _HANGOUTS_API_URL,
148        'mas_one_platform_url': _MEETINGS_API_URL
149      },
150      'conference': {
151        'conference_code': meeting_code
152      },
153      'bot_type': "MEETINGS",
154      'use_random_video_file_for_playback': True
155    }
156
157    resp = requests.post(
158        '%s/v1/conference/%s/bots:add' % (_BOND_API_URL, meeting_code),
159        headers={
160            'Content-Type': 'application/json',
161            'Authorization': 'Bearer %s' % token
162        },
163        data=json.dumps(request_data))
164
165    json_response = json.loads(resp.text)
166    logging.info("AddBotsRequest response: %s", json_response)
167    return json_response["botIds"]
168
169  def _GetAccessToken(self):
170    if self._last_token is None or self._CheckTokenExpired():
171      credentials = self._CreateApiCredentials()
172      scope = 'https://www.googleapis.com/auth/meetings'
173      credentials = credentials.create_scoped(scope)
174      self._last_token_request_time = datetime.now()
175      self._last_token = credentials.get_access_token().access_token
176    return self._last_token
177
178  def _CreateApiCredentials(self):
179    return GoogleCredentials.from_stream(_SERVICE_CREDS_FILE)
180
181  def _CheckTokenExpired(self):
182    elapsed = datetime.now() - self._last_token_request_time
183    return elapsed.total_seconds() > _TOKEN_TTL_SECONDS
184